diff --git a/InstructionSets/M68k/Implementation/PerformImplementation.hpp b/InstructionSets/M68k/Implementation/PerformImplementation.hpp index c5f7fda1c..95a484e30 100644 --- a/InstructionSets/M68k/Implementation/PerformImplementation.hpp +++ b/InstructionSets/M68k/Implementation/PerformImplementation.hpp @@ -9,6 +9,7 @@ #ifndef InstructionSets_M68k_PerformImplementation_h #define InstructionSets_M68k_PerformImplementation_h +#include "../../../Numeric/Carry.hpp" #include "../ExceptionVectors.hpp" #include @@ -25,28 +26,6 @@ inline int32_t s_extend16(uint16_t x) { return int32_t(int16_t(x)); } namespace Primitive { -/// @returns An int of type @c IntT with only the most-significant bit set. -template constexpr IntT top_bit() { - static_assert(!std::numeric_limits::is_signed); - constexpr IntT max = std::numeric_limits::max(); - return max - (max >> 1); -} - -/// @returns An int with the top bit indicating whether overflow occurred when @c source and @c destination -/// were either added (if @c is_add is true) or subtracted (if @c is_add is false) and the result was @c result. -/// All other bits will be clear. -template -static Status::FlagT overflow(IntT source, IntT destination, IntT result) { - const IntT output_changed = result ^ destination; - const IntT input_differed = source ^ destination; - - if constexpr (is_add) { - return top_bit() & output_changed & ~input_differed; - } else { - return top_bit() & output_changed & input_differed; - } -} - /// Performs an add or subtract (as per @c is_add) between @c source and @c destination, /// updating @c status. @c is_extend indicates whether this is an extend operation (e.g. ADDX) /// or a plain one (e.g. ADD). @@ -81,7 +60,7 @@ static void add_sub(IntT source, IntT &destination, Status &status) { status.zero_result = Status::FlagT(result); } status.set_negative(result); - status.overflow_flag = overflow(source, destination, result); + status.overflow_flag = Numeric::overflow(destination, source, result); destination = result; } @@ -143,7 +122,7 @@ void compare(IntT source, IntT destination, Status &status) { const IntT result = destination - source; status.carry_flag = result > destination; status.set_neg_zero(result); - status.overflow_flag = Primitive::overflow(source, destination, result); + status.overflow_flag = Numeric::overflow(destination, source, result); } /// @returns the name of the bit to be used as a mask for BCLR, BCHG, BSET or BTST for @@ -293,7 +272,7 @@ template void negative(IntT &source, Status &sta } status.extend_flag = status.carry_flag = result; // i.e. any value other than 0 will result in carry. status.set_negative(result); - status.overflow_flag = Primitive::overflow(source, IntT(0), result); + status.overflow_flag = Numeric::overflow(IntT(0), source, result); source = result; } @@ -311,11 +290,6 @@ template int shift_count(uint8_t source return count; } -/// @returns The number of bits in @c IntT. -template constexpr int bit_size() { - return sizeof(IntT) * 8; -} - /// Perform an arithmetic or logical shift, i.e. any of LSL, LSR, ASL or ASR. template void shift(uint32_t source, IntT &destination, Status &status, FlowController &flow_controller) { static_assert( @@ -325,7 +299,7 @@ template void shif operation == Operation::LSRb || operation == Operation::LSRw || operation == Operation::LSRl ); - constexpr auto size = bit_size(); + constexpr auto size = Numeric::bit_size(); const auto shift = shift_count(uint8_t(source), flow_controller); if(!shift) { @@ -355,7 +329,7 @@ template void shif if(shift > size) { status.carry_flag = status.extend_flag = 0; } else { - status.carry_flag = status.extend_flag = (destination << (shift - 1)) & top_bit(); + status.carry_flag = status.extend_flag = (destination << (shift - 1)) & Numeric::top_bit(); } if(type == Type::LSL) { @@ -370,7 +344,7 @@ template void shif // For a shift of n places, overflow will be set if the top n+1 bits were not // all the same value. const auto affected_bits = IntT( - ~((top_bit() >> shift) - 1) + ~((Numeric::top_bit() >> shift) - 1) ); // e.g. shift = 1 => ~((0x80 >> 1) - 1) = ~(0x40 - 1) = ~0x3f = 0xc0, i.e. if shift is // 1 then the top two bits are relevant to whether there was overflow. If they have the // same value, i.e. are both 0 or are both 1, then there wasn't. Otherwise there was. @@ -396,7 +370,7 @@ template void shif const IntT sign_word = type == Type::LSR ? - 0 : (destination & top_bit() ? IntT(~0) : 0); + 0 : (destination & Numeric::top_bit() ? IntT(~0) : 0); if(shift >= size) { destination = sign_word; @@ -417,7 +391,7 @@ template void rota operation == Operation::RORb || operation == Operation::RORw || operation == Operation::RORl ); - constexpr auto size = bit_size(); + constexpr auto size = Numeric::bit_size(); auto shift = shift_count(uint8_t(source), flow_controller); if(!shift) { @@ -442,7 +416,7 @@ template void rota (destination << (size - shift)) ); } - status.carry_flag = Status::FlagT(destination & top_bit()); + status.carry_flag = Status::FlagT(destination & Numeric::top_bit()); break; } } @@ -458,7 +432,7 @@ template void rox( operation == Operation::ROXRb || operation == Operation::ROXRw || operation == Operation::ROXRl ); - constexpr auto size = bit_size(); + constexpr auto size = Numeric::bit_size(); auto shift = shift_count(uint8_t(source), flow_controller) % (size + 1); if(!shift) { @@ -469,9 +443,9 @@ template void rox( case Operation::ROXLb: case Operation::ROXLw: case Operation::ROXLl: status.carry_flag = Status::FlagT((destination >> (size - shift)) & 1); - if(shift == bit_size()) { + if(shift == Numeric::bit_size()) { destination = IntT( - (status.extend_flag ? top_bit() : 0) | + (status.extend_flag ? Numeric::top_bit() : 0) | (destination >> 1) ); } else if(shift == 1) { @@ -491,7 +465,7 @@ template void rox( case Operation::ROXRb: case Operation::ROXRw: case Operation::ROXRl: status.carry_flag = Status::FlagT(destination & (1 << (shift - 1))); - if(shift == bit_size()) { + if(shift == Numeric::bit_size()) { destination = IntT( (status.extend_flag ? 1 : 0) | (destination << 1) @@ -499,12 +473,12 @@ template void rox( } else if(shift == 1) { destination = IntT( (destination >> 1) | - (status.extend_flag ? top_bit() : 0) + (status.extend_flag ? Numeric::top_bit() : 0) ); } else { destination = IntT( (destination >> shift) | - ((status.extend_flag ? top_bit() : 0) >> (shift - 1)) | + ((status.extend_flag ? Numeric::top_bit() : 0) >> (shift - 1)) | (destination << (size + 1 - shift)) ); } @@ -882,14 +856,14 @@ template < Shifts and rotates. */ case Operation::ASLm: - status.extend_flag = status.carry_flag = src.w & Primitive::top_bit(); - status.overflow_flag = (src.w ^ (src.w << 1)) & Primitive::top_bit(); + status.extend_flag = status.carry_flag = src.w & Numeric::top_bit(); + status.overflow_flag = (src.w ^ (src.w << 1)) & Numeric::top_bit(); src.w <<= 1; status.set_neg_zero(src.w); break; case Operation::LSLm: - status.extend_flag = status.carry_flag = src.w & Primitive::top_bit(); + status.extend_flag = status.carry_flag = src.w & Numeric::top_bit(); status.overflow_flag = 0; src.w <<= 1; status.set_neg_zero(src.w); @@ -898,7 +872,7 @@ template < case Operation::ASRm: status.extend_flag = status.carry_flag = src.w & 1; status.overflow_flag = 0; - src.w = (src.w & Primitive::top_bit()) | (src.w >> 1); + src.w = (src.w & Numeric::top_bit()) | (src.w >> 1); status.set_neg_zero(src.w); break; @@ -918,13 +892,13 @@ template < case Operation::RORm: src.w = uint16_t((src.w >> 1) | (src.w << 15)); - status.carry_flag = src.w & Primitive::top_bit(); + status.carry_flag = src.w & Numeric::top_bit(); status.overflow_flag = 0; status.set_neg_zero(src.w); break; case Operation::ROXLm: - status.carry_flag = src.w & Primitive::top_bit(); + status.carry_flag = src.w & Numeric::top_bit(); src.w = uint16_t((src.w << 1) | (status.extend_flag ? 0x0001 : 0x0000)); status.extend_flag = status.carry_flag; status.overflow_flag = 0; diff --git a/InstructionSets/x86/DataPointerResolver.hpp b/InstructionSets/x86/DataPointerResolver.hpp deleted file mode 100644 index 056aa9476..000000000 --- a/InstructionSets/x86/DataPointerResolver.hpp +++ /dev/null @@ -1,318 +0,0 @@ -// -// DataPointerResolver.hpp -// Clock Signal -// -// Created by Thomas Harte on 24/02/2022. -// Copyright © 2022 Thomas Harte. All rights reserved. -// - -#ifndef DataPointerResolver_hpp -#define DataPointerResolver_hpp - -#include "Instruction.hpp" -#include "Model.hpp" - -#include - -namespace InstructionSet::x86 { - -/// Unlike source, describes only registers, and breaks -/// them down by conventional name — so AL, AH, AX and EAX are all -/// listed separately and uniquely, rather than being eAX+size or -/// eSPorAH with a size of 1. -enum class Register: uint8_t { - // 8-bit registers. - AL, AH, - CL, CH, - DL, DH, - BL, BH, - - // 16-bit registers. - AX, CX, DX, BX, - SP, BP, SI, DI, - ES, CS, SS, DS, - FS, GS, - - // 32-bit registers. - EAX, ECX, EDX, EBX, - ESP, EBP, ESI, EDI, - - // - None -}; - -/// @returns @c true if @c r is the same size as @c DataT; @c false otherwise. -/// @discussion Provided primarily to aid in asserts; if the decoder and resolver are both -/// working then it shouldn't be necessary to test this in register files. -template constexpr bool is_sized(Register r) { - static_assert(sizeof(DataT) == 4 || sizeof(DataT) == 2 || sizeof(DataT) == 1); - - if constexpr (sizeof(DataT) == 4) { - return r >= Register::EAX && r < Register::None; - } - - if constexpr (sizeof(DataT) == 2) { - return r >= Register::AX && r < Register::EAX; - } - - if constexpr (sizeof(DataT) == 1) { - return r >= Register::AL && r < Register::AX; - } - - return false; -} - -/// @returns the proper @c Register given @c source and data of size @c sizeof(DataT), -/// or Register::None if no such register exists (e.g. asking for a 32-bit version of CS). -template constexpr Register register_for_source(Source source) { - static_assert(sizeof(DataT) == 4 || sizeof(DataT) == 2 || sizeof(DataT) == 1); - - if constexpr (sizeof(DataT) == 4) { - switch(source) { - case Source::eAX: return Register::EAX; - case Source::eCX: return Register::ECX; - case Source::eDX: return Register::EDX; - case Source::eBX: return Register::EBX; - case Source::eSPorAH: return Register::ESP; - case Source::eBPorCH: return Register::EBP; - case Source::eSIorDH: return Register::ESI; - case Source::eDIorBH: return Register::EDI; - - default: break; - } - } - - if constexpr (sizeof(DataT) == 2) { - switch(source) { - case Source::eAX: return Register::AX; - case Source::eCX: return Register::CX; - case Source::eDX: return Register::DX; - case Source::eBX: return Register::BX; - case Source::eSPorAH: return Register::SP; - case Source::eBPorCH: return Register::BP; - case Source::eSIorDH: return Register::SI; - case Source::eDIorBH: return Register::DI; - case Source::ES: return Register::ES; - case Source::CS: return Register::CS; - case Source::SS: return Register::SS; - case Source::DS: return Register::DS; - case Source::FS: return Register::FS; - case Source::GS: return Register::GS; - - default: break; - } - } - - if constexpr (sizeof(DataT) == 1) { - switch(source) { - case Source::eAX: return Register::AL; - case Source::eCX: return Register::CL; - case Source::eDX: return Register::DL; - case Source::eBX: return Register::BL; - case Source::eSPorAH: return Register::AH; - case Source::eBPorCH: return Register::CH; - case Source::eSIorDH: return Register::DH; - case Source::eDIorBH: return Register::BH; - - default: break; - } - } - - return Register::None; -} - -/// Reads from or writes to the source or target identified by a DataPointer, relying upon two user-supplied classes: -/// -/// * a register bank; and -/// * a memory pool. -/// -/// The register bank should implement `template DataT read()` and `template void write(DataT)`. -/// Those functions will be called only with registers and data types that are appropriate to the @c model. -/// -/// The memory pool should implement `template DataT read(Source segment, uint32_t address)` and -/// `template void write(Source segment, uint32_t address, DataT value)`. -template class DataPointerResolver { - public: - public: - /// Reads the data pointed to by @c pointer, referencing @c instruction, @c memory and @c registers as necessary. - template static DataT read( - RegistersT ®isters, - MemoryT &memory, - const Instruction &instruction, - DataPointer pointer); - - /// Writes @c value to the data pointed to by @c pointer, referencing @c instruction, @c memory and @c registers as necessary. - template static void write( - RegistersT ®isters, - MemoryT &memory, - const Instruction &instruction, - DataPointer pointer, - DataT value); - - /// Computes the effective address of @c pointer including any displacement applied by @c instruction. - /// @c pointer must be of type Source::Indirect. - template - static uint32_t effective_address( - RegistersT ®isters, - const Instruction &instruction, - DataPointer pointer); - - private: - template static void access( - RegistersT ®isters, - MemoryT &memory, - const Instruction &instruction, - DataPointer pointer, - DataT &value); -}; - - -// -// Implementation begins here. -// - -template -template DataT DataPointerResolver::read( - RegistersT ®isters, - MemoryT &memory, - const Instruction &instruction, - DataPointer pointer) { - DataT result; - access(registers, memory, instruction, pointer, result); - return result; - } - -template -template void DataPointerResolver::write( - RegistersT ®isters, - MemoryT &memory, - const Instruction &instruction, - DataPointer pointer, - DataT value) { - access(registers, memory, instruction, pointer, value); - } - -#define rw(v, r, is_write) \ - case Source::r: \ - using VType = typename std::remove_reference::type; \ - if constexpr (is_write) { \ - registers.template write(Source::r)>(v); \ - } else { \ - v = registers.template read(Source::r)>(); \ - } \ - break; - -#define ALLREGS(v, i) rw(v, eAX, i); rw(v, eCX, i); \ - rw(v, eDX, i); rw(v, eBX, i); \ - rw(v, eSPorAH, i); rw(v, eBPorCH, i); \ - rw(v, eSIorDH, i); rw(v, eDIorBH, i); \ - rw(v, ES, i); rw(v, CS, i); \ - rw(v, SS, i); rw(v, DS, i); \ - rw(v, FS, i); rw(v, GS, i); - -template -template -uint32_t DataPointerResolver::effective_address( - RegistersT ®isters, - const Instruction &instruction, - DataPointer pointer) { - using AddressT = typename Instruction::AddressT; - AddressT base = 0, index = 0; - - if constexpr (has_base) { - switch(pointer.base()) { - default: break; - ALLREGS(base, false); - } - } - - switch(pointer.index()) { - default: break; - ALLREGS(index, false); - } - - uint32_t address = index; - if constexpr (model >= Model::i80386) { - address <<= pointer.scale(); - } else { - assert(!pointer.scale()); - } - - // Always compute address as 32-bit. - // TODO: verify use of memory_mask around here. - // Also I think possibly an exception is supposed to be generated - // if the programmer is in 32-bit mode and has asked for 16-bit - // address computation but generated e.g. a 17-bit result. Look into - // that when working on execution. For now the goal is merely decoding - // and this code exists both to verify the presence of all necessary - // fields and to help to explore the best breakdown of storage - // within Instruction. - constexpr uint32_t memory_masks[] = {0x0000'ffff, 0xffff'ffff}; - const uint32_t memory_mask = memory_masks[int(instruction.address_size())]; - address = (address & memory_mask) + (base & memory_mask) + instruction.displacement(); - return address; - } - -template -template void DataPointerResolver::access( - RegistersT ®isters, - MemoryT &memory, - const Instruction &instruction, - DataPointer pointer, - DataT &value) { - const Source source = pointer.source(); - - switch(source) { - default: - if constexpr (!is_write) { - value = 0; - } - return; - - ALLREGS(value, is_write); - - case Source::DirectAddress: - if constexpr(is_write) { - memory.template write(instruction.data_segment(), instruction.displacement(), value); - } else { - value = memory.template read(instruction.data_segment(), instruction.displacement()); - } - break; - case Source::Immediate: - value = DataT(instruction.operand()); - break; - -#define indirect(has_base) { \ - const auto address = effective_address \ - (registers, instruction, pointer); \ - \ - if constexpr (is_write) { \ - memory.template write( \ - instruction.data_segment(), \ - address, \ - value \ - ); \ - } else { \ - value = memory.template read( \ - instruction.data_segment(), \ - address \ - ); \ - } \ -} - case Source::IndirectNoBase: - indirect(false); - break; - - case Source::Indirect: - indirect(true); - break; -#undef indirect - - } - } -#undef ALLREGS -#undef rw - -} - -#endif /* DataPointerResolver_hpp */ diff --git a/InstructionSets/x86/Decoder.cpp b/InstructionSets/x86/Decoder.cpp index a8c3f92d4..21d3370ab 100644 --- a/InstructionSets/x86/Decoder.cpp +++ b/InstructionSets/x86/Decoder.cpp @@ -80,7 +80,7 @@ std::pair::InstructionT> Decoder::decode(con #define Displacement(op, size) \ SetOperation(Operation::op); \ phase_ = Phase::DisplacementOrOperand; \ - displacement_size_ = size + operation_size_= displacement_size_ = size /// Handles PUSH [immediate], etc — anything with only an immediate operand. #define Immediate(op, size) \ @@ -90,11 +90,11 @@ std::pair::InstructionT> Decoder::decode(con operand_size_ = size /// Handles far CALL and far JMP — fixed four or six byte operand operations. -#define Far(op) \ - SetOperation(Operation::op); \ - phase_ = Phase::DisplacementOrOperand; \ - operand_size_ = DataSize::Word; \ - destination_ = Source::Immediate; \ +#define Far(op) \ + SetOperation(Operation::op); \ + phase_ = Phase::DisplacementOrOperand; \ + operation_size_ = operand_size_ = DataSize::Word; \ + destination_ = Source::Immediate; \ displacement_size_ = data_size(default_address_size_) /// Handles ENTER — a fixed three-byte operation. @@ -353,7 +353,7 @@ std::pair::InstructionT> Decoder::decode(con case 0x8e: MemRegReg(MOV, Seg_MemReg, DataSize::Word); break; case 0x8f: MemRegReg(POP, MemRegSingleOperand, data_size_); break; - case 0x90: Complete(NOP, None, None, DataSize::None); break; // Or XCHG AX, AX? + case 0x90: Complete(NOP, None, None, DataSize::Byte); break; // Could be encoded as XCHG AX, AX if Operation space becomes limited. case 0x91: Complete(XCHG, eAX, eCX, data_size_); break; case 0x92: Complete(XCHG, eAX, eDX, data_size_); break; case 0x93: Complete(XCHG, eAX, eBX, data_size_); break; @@ -365,7 +365,7 @@ std::pair::InstructionT> Decoder::decode(con case 0x98: Complete(CBW, eAX, AH, data_size_); break; case 0x99: Complete(CWD, eAX, eDX, data_size_); break; case 0x9a: Far(CALLfar); break; - case 0x9b: Complete(WAIT, None, None, DataSize::None); break; + case 0x9b: Complete(WAIT, None, None, DataSize::Byte); break; case 0x9c: Complete(PUSHF, None, None, data_size_); break; case 0x9d: Complete(POPF, None, None, data_size_); break; case 0x9e: Complete(SAHF, None, None, DataSize::Byte); break; @@ -421,11 +421,11 @@ std::pair::InstructionT> Decoder::decode(con source_ = Source::Immediate; operand_size_ = data_size_; } else { - Complete(RETnear, None, None, DataSize::None); + Complete(RETnear, None, None, DataSize::Byte); } break; case 0xc2: RegData(RETnear, None, data_size_); break; - case 0xc3: Complete(RETnear, None, None, DataSize::None); break; + case 0xc3: Complete(RETnear, None, None, DataSize::Byte); break; case 0xc4: MemRegReg(LES, Reg_MemReg, data_size_); break; case 0xc5: MemRegReg(LDS, Reg_MemReg, data_size_); break; case 0xc6: MemRegReg(MOV, MemRegMOV, DataSize::Byte); break; @@ -440,14 +440,14 @@ std::pair::InstructionT> Decoder::decode(con break; case 0xc9: if constexpr (model >= Model::i80186) { - Complete(LEAVE, None, None, DataSize::None); + Complete(LEAVE, None, None, DataSize::Byte); } else { - Complete(RETfar, None, None, DataSize::DWord); + Complete(RETfar, None, None, DataSize::Word); } break; case 0xca: RegData(RETfar, None, data_size_); break; - case 0xcb: Complete(RETfar, None, None, DataSize::DWord); break; + case 0xcb: Complete(RETfar, None, None, DataSize::Word); break; case 0xcc: // Encode INT3 as though it were INT with an @@ -456,8 +456,8 @@ std::pair::InstructionT> Decoder::decode(con operand_ = 3; break; case 0xcd: RegData(INT, None, DataSize::Byte); break; - case 0xce: Complete(INTO, None, None, DataSize::None); break; - case 0xcf: Complete(IRET, None, None, DataSize::None); break; + case 0xce: Complete(INTO, None, None, DataSize::Byte); break; + case 0xcf: Complete(IRET, None, None, DataSize::Byte); break; case 0xd0: case 0xd1: ShiftGroup(); @@ -505,17 +505,17 @@ std::pair::InstructionT> Decoder::decode(con case 0xf2: repetition_ = Repetition::RepNE; break; case 0xf3: repetition_ = Repetition::RepE; break; - case 0xf4: Complete(HLT, None, None, DataSize::None); break; - case 0xf5: Complete(CMC, None, None, DataSize::None); break; + case 0xf4: Complete(HLT, None, None, DataSize::Byte); break; + case 0xf5: Complete(CMC, None, None, DataSize::Byte); break; case 0xf6: MemRegReg(Invalid, MemRegTEST_to_IDIV, DataSize::Byte); break; case 0xf7: MemRegReg(Invalid, MemRegTEST_to_IDIV, data_size_); break; - case 0xf8: Complete(CLC, None, None, DataSize::None); break; - case 0xf9: Complete(STC, None, None, DataSize::None); break; - case 0xfa: Complete(CLI, None, None, DataSize::None); break; - case 0xfb: Complete(STI, None, None, DataSize::None); break; - case 0xfc: Complete(CLD, None, None, DataSize::None); break; - case 0xfd: Complete(STD, None, None, DataSize::None); break; + case 0xf8: Complete(CLC, None, None, DataSize::Byte); break; + case 0xf9: Complete(STC, None, None, DataSize::Byte); break; + case 0xfa: Complete(CLI, None, None, DataSize::Byte); break; + case 0xfb: Complete(STI, None, None, DataSize::Byte); break; + case 0xfc: Complete(CLD, None, None, DataSize::Byte); break; + case 0xfd: Complete(STD, None, None, DataSize::Byte); break; case 0xfe: MemRegReg(Invalid, MemRegINC_DEC, DataSize::Byte); break; case 0xff: MemRegReg(Invalid, MemRegINC_to_PUSH, data_size_); break; @@ -541,7 +541,7 @@ std::pair::InstructionT> Decoder::decode(con case 0x03: MemRegReg(LSL, Reg_MemReg, data_size_); break; case 0x05: Requires(i80286); - Complete(LOADALL, None, None, DataSize::None); + Complete(LOADALL, None, None, DataSize::Byte); break; case 0x06: Complete(CLTS, None, None, DataSize::Byte); break; @@ -885,7 +885,6 @@ std::pair::InstructionT> Decoder::decode(con case 4: SetOperation(Operation::JMPabs); break; case 5: SetOperation(Operation::JMPfar); break; } - // TODO: CALLfar and JMPfar aren't correct above; find out what is. break; case ModRegRMFormat::MemRegSingleOperand: @@ -1015,23 +1014,11 @@ std::pair::InstructionT> Decoder::decode(con if(bytes_to_consume == outstanding_bytes) { phase_ = Phase::ReadyToPost; - // TODO: whether the displacement is signed appears to depend on the opcode. - // Find an appropriate table. - - if(!sign_extend_displacement_) { - switch(displacement_size_) { - case DataSize::None: displacement_ = 0; break; - case DataSize::Byte: displacement_ = decltype(displacement_)(uint8_t(inward_data_)); break; - case DataSize::Word: displacement_ = decltype(displacement_)(uint16_t(inward_data_)); break; - case DataSize::DWord: displacement_ = decltype(displacement_)(uint32_t(inward_data_)); break; - } - } else { - switch(displacement_size_) { - case DataSize::None: displacement_ = 0; break; - case DataSize::Byte: displacement_ = int8_t(inward_data_); break; - case DataSize::Word: displacement_ = int16_t(inward_data_); break; - case DataSize::DWord: displacement_ = int32_t(inward_data_); break; - } + switch(displacement_size_) { + case DataSize::None: displacement_ = 0; break; + case DataSize::Byte: displacement_ = int8_t(inward_data_); break; + case DataSize::Word: displacement_ = int16_t(inward_data_); break; + case DataSize::DWord: displacement_ = int32_t(inward_data_); break; } inward_data_ >>= bit_size(displacement_size_); @@ -1066,7 +1053,7 @@ std::pair::InstructionT> Decoder::decode(con address_size_, segment_override_, repetition_, - DataSize(operation_size_), + operation_size_, static_cast(displacement_), static_cast(operand_), consumed_ diff --git a/InstructionSets/x86/Decoder.hpp b/InstructionSets/x86/Decoder.hpp index 354d4accd..eaaad84e5 100644 --- a/InstructionSets/x86/Decoder.hpp +++ b/InstructionSets/x86/Decoder.hpp @@ -195,8 +195,6 @@ template class Decoder { bool sign_extend_operand_ = false; // If set then sign extend the operand up to the operation size; // otherwise it'll be zero-padded. - bool sign_extend_displacement_ = false; // Much as above; 'displacement' is used internally for both - // displacements and offsets, so signage will vary. // Prefix capture fields. Repetition repetition_ = Repetition::None; @@ -225,7 +223,6 @@ template class Decoder { next_inward_data_shift_ = 0; inward_data_ = 0; sign_extend_operand_ = false; - sign_extend_displacement_ = false; } }; diff --git a/InstructionSets/x86/Documentation/80386 opcode map.html b/InstructionSets/x86/Documentation/80386 opcode map.html index 3bab74c42..09e6330e5 100644 --- a/InstructionSets/x86/Documentation/80386 opcode map.html +++ b/InstructionSets/x86/Documentation/80386 opcode map.html @@ -224,7 +224,7 @@ AND SEG =ES - POP ES + DAA SUB SEG =CS DAS diff --git a/InstructionSets/x86/Implementation/PerformImplementation.hpp b/InstructionSets/x86/Implementation/PerformImplementation.hpp new file mode 100644 index 000000000..eb76cc5fb --- /dev/null +++ b/InstructionSets/x86/Implementation/PerformImplementation.hpp @@ -0,0 +1,1872 @@ +// +// +// PerformImplementation.hpp +// Clock Signal +// +// Created by Thomas Harte on 05/10/2023. +// Copyright © 2023 Thomas Harte. All rights reserved. +// + +#ifndef PerformImplementation_h +#define PerformImplementation_h + +#include "../../../Numeric/Carry.hpp" +#include "../../../Numeric/RegisterSizes.hpp" +#include "../Interrupts.hpp" + +#include + +namespace InstructionSet::x86 { + +template +IntT *resolve( + InstructionT &instruction, + Source source, + DataPointer pointer, + RegistersT ®isters, + MemoryT &memory, + IntT *none = nullptr, + IntT *immediate = nullptr +); + +template +uint32_t address( + InstructionT &instruction, + DataPointer pointer, + RegistersT ®isters, + MemoryT &memory +) { + // TODO: non-word indexes and bases. + if constexpr (source == Source::DirectAddress) { + return instruction.offset(); + } + + uint32_t address; + uint16_t zero = 0; + address = *resolve(instruction, pointer.index(), pointer, registers, memory, &zero); + if constexpr (is_32bit(model)) { + address <<= pointer.scale(); + } + address += instruction.offset(); + + if constexpr (source == Source::IndirectNoBase) { + return address; + } + return address + *resolve(instruction, pointer.base(), pointer, registers, memory); +} + +template +IntT *register_(RegistersT ®isters) { + switch(source) { + case Source::eAX: + // Slightly contorted if chain here and below: + // + // (i) does the `constexpr` version of a `switch`; and + // (i) ensures .eax() etc aren't called on @c registers for 16-bit processors, so they need not implement 32-bit storage. + if constexpr (is_32bit(model) && std::is_same_v) { return ®isters.eax(); } + else if constexpr (std::is_same_v) { return ®isters.ax(); } + else if constexpr (std::is_same_v) { return ®isters.al(); } + else { return nullptr; } + case Source::eCX: + if constexpr (is_32bit(model) && std::is_same_v) { return ®isters.ecx(); } + else if constexpr (std::is_same_v) { return ®isters.cx(); } + else if constexpr (std::is_same_v) { return ®isters.cl(); } + else { return nullptr; } + case Source::eDX: + if constexpr (is_32bit(model) && std::is_same_v) { return ®isters.edx(); } + else if constexpr (std::is_same_v) { return ®isters.dx(); } + else if constexpr (std::is_same_v) { return ®isters.dl(); } + else if constexpr (std::is_same_v) { return nullptr; } + case Source::eBX: + if constexpr (is_32bit(model) && std::is_same_v) { return ®isters.ebx(); } + else if constexpr (std::is_same_v) { return ®isters.bx(); } + else if constexpr (std::is_same_v) { return ®isters.bl(); } + else if constexpr (std::is_same_v) { return nullptr; } + case Source::eSPorAH: + if constexpr (is_32bit(model) && std::is_same_v) { return ®isters.esp(); } + else if constexpr (std::is_same_v) { return ®isters.sp(); } + else if constexpr (std::is_same_v) { return ®isters.ah(); } + else { return nullptr; } + case Source::eBPorCH: + if constexpr (is_32bit(model) && std::is_same_v) { return ®isters.ebp(); } + else if constexpr (std::is_same_v) { return ®isters.bp(); } + else if constexpr (std::is_same_v) { return ®isters.ch(); } + else { return nullptr; } + case Source::eSIorDH: + if constexpr (is_32bit(model) && std::is_same_v) { return ®isters.esi(); } + else if constexpr (std::is_same_v) { return ®isters.si(); } + else if constexpr (std::is_same_v) { return ®isters.dh(); } + else { return nullptr; } + case Source::eDIorBH: + if constexpr (is_32bit(model) && std::is_same_v) { return ®isters.edi(); } + else if constexpr (std::is_same_v) { return ®isters.di(); } + else if constexpr (std::is_same_v) { return ®isters.bh(); } + else { return nullptr; } + + default: return nullptr; + } +} + +template +uint32_t address( + InstructionT &instruction, + DataPointer pointer, + RegistersT ®isters, + MemoryT &memory +) { + switch(pointer.source()) { + default: return 0; + case Source::eAX: return *register_(registers); + case Source::eCX: return *register_(registers); + case Source::eDX: return *register_(registers); + case Source::eBX: return *register_(registers); + case Source::eSPorAH: return *register_(registers); + case Source::eBPorCH: return *register_(registers); + case Source::eSIorDH: return *register_(registers); + case Source::eDIorBH: return *register_(registers); + case Source::Indirect: return address(instruction, pointer, registers, memory); + case Source::IndirectNoBase: return address(instruction, pointer, registers, memory); + case Source::DirectAddress: return address(instruction, pointer, registers, memory); + } +} + +template +IntT *resolve( + InstructionT &instruction, + Source source, + DataPointer pointer, + RegistersT ®isters, + MemoryT &memory, + IntT *none, + IntT *immediate +) { + // Rules: + // + // * if this is a memory access, set target_address and break; + // * otherwise return the appropriate value. + uint32_t target_address; + switch(source) { + case Source::eAX: return register_(registers); + case Source::eCX: return register_(registers); + case Source::eDX: return register_(registers); + case Source::eBX: return register_(registers); + case Source::eSPorAH: return register_(registers); + case Source::eBPorCH: return register_(registers); + case Source::eSIorDH: return register_(registers); + case Source::eDIorBH: return register_(registers); + + // Segment registers are always 16-bit. + case Source::ES: if constexpr (std::is_same_v) return ®isters.es(); else return nullptr; + case Source::CS: if constexpr (std::is_same_v) return ®isters.cs(); else return nullptr; + case Source::SS: if constexpr (std::is_same_v) return ®isters.ss(); else return nullptr; + case Source::DS: if constexpr (std::is_same_v) return ®isters.ds(); else return nullptr; + + // 16-bit models don't have FS and GS. + case Source::FS: if constexpr (is_32bit(model) && std::is_same_v) return ®isters.fs(); else return nullptr; + case Source::GS: if constexpr (is_32bit(model) && std::is_same_v) return ®isters.gs(); else return nullptr; + + case Source::Immediate: + *immediate = instruction.operand(); + return immediate; + + case Source::None: return none; + + case Source::Indirect: + target_address = address(instruction, pointer, registers, memory); + break; + case Source::IndirectNoBase: + target_address = address(instruction, pointer, registers, memory); + break; + case Source::DirectAddress: + target_address = address(instruction, pointer, registers, memory); + break; + } + + // If execution has reached here then a memory fetch is required. + // Do it and exit. + const Source segment = pointer.segment(instruction.segment_override()); + return &memory.template access(segment, target_address); +}; + +namespace Primitive { + +// The below takes a reference in order properly to handle PUSH SP, which should place the value of SP after the +// push onto the stack. +template +void push(IntT &value, MemoryT &memory, RegistersT ®isters) { + registers.sp_ -= sizeof(IntT); + memory.template access( + InstructionSet::x86::Source::SS, + registers.sp_) = value; + memory.template write_back(); +} + +template +IntT pop(MemoryT &memory, RegistersT ®isters) { + const auto value = memory.template access( + InstructionSet::x86::Source::SS, + registers.sp_); + registers.sp_ += sizeof(IntT); + return value; +} + +// +// Comments below on intended functioning of each operation come from the 1997 edition of the +// Intel Architecture Software Developer’s Manual; that year all such definitions still fitted within a +// single volume, Volume 2. +// +// Order Number 243191; e.g. https://www.ardent-tool.com/CPU/docs/Intel/IA/243191-002.pdf +// + +inline void aaa(CPU::RegisterPair16 &ax, Status &status) { // P. 313 + /* + IF ((AL AND 0FH) > 9) OR (AF = 1) + THEN + AL ← (AL + 6); + AH ← AH + 1; + AF ← 1; + CF ← 1; + ELSE + AF ← 0; + CF ← 0; + FI; + AL ← AL AND 0FH; + */ + /* + The AF and CF flags are set to 1 if the adjustment results in a decimal carry; + otherwise they are cleared to 0. The OF, SF, ZF, and PF flags are undefined. + */ + if((ax.halves.low & 0x0f) > 9 || status.flag()) { + ax.halves.low += 6; + ++ax.halves.high; + status.set_from(1); + } else { + status.set_from(0); + } + ax.halves.low &= 0x0f; +} + +inline void aad(CPU::RegisterPair16 &ax, uint8_t imm, Status &status) { + /* + tempAL ← AL; + tempAH ← AH; + AL ← (tempAL + (tempAH * imm8)) AND FFH; (* imm8 is set to 0AH for the AAD mnemonic *) + AH ← 0 + */ + /* + The SF, ZF, and PF flags are set according to the result; + the OF, AF, and CF flags are undefined. + */ + ax.halves.low = ax.halves.low + (ax.halves.high * imm); + ax.halves.high = 0; + status.set_from(ax.halves.low); +} + +template +void aam(CPU::RegisterPair16 &ax, uint8_t imm, Status &status, FlowControllerT &flow_controller) { + /* + tempAL ← AL; + AH ← tempAL / imm8; (* imm8 is set to 0AH for the AAD mnemonic *) + AL ← tempAL MOD imm8; + */ + /* + The SF, ZF, and PF flags are set according to the result. + The OF, AF, and CF flags are undefined. + */ + /* + If ... an immediate value of 0 is used, it will cause a #DE (divide error) exception. + */ + if(!imm) { + flow_controller.interrupt(Interrupt::DivideError); + return; + } + + ax.halves.high = ax.halves.low / imm; + ax.halves.low = ax.halves.low % imm; + status.set_from(ax.halves.low); +} + +inline void aas(CPU::RegisterPair16 &ax, Status &status) { + /* + IF ((AL AND 0FH) > 9) OR (AF = 1) + THEN + AL ← AL – 6; + AH ← AH – 1; + AF ← 1; + CF ← 1; + ELSE + CF ← 0; + AF ← 0; + FI; + AL ← AL AND 0FH; + */ + /* + The AF and CF flags are set to 1 if there is a decimal borrow; + otherwise, they are cleared to 0. The OF, SF, ZF, and PF flags are undefined. + */ + if((ax.halves.low & 0x0f) > 9 || status.flag()) { + ax.halves.low -= 6; + --ax.halves.high; + status.set_from(1); + } else { + status.set_from(0); + } + ax.halves.low &= 0x0f; +} + +inline void daa(uint8_t &al, Status &status) { + /* + (as modified by https://www.felixcloutier.com/x86/daa ...) + + old_AL ← AL; + old_CF ← CF; + CF ← 0; + + IF (((AL AND 0FH) > 9) or AF = 1) + THEN + AL ← AL + 6; + CF ← old_CF OR CarryFromLastAddition; (* CF OR carry from AL ← AL + 6 *) + AF ← 1; + ELSE + AF ← 0; + FI; + IF ((old_AL > 99H) or old_CF = 1) + THEN + AL ← AL + 60H; + CF ← 1; + ELSE + CF ← 0; + FI; + */ + /* + The CF and AF flags are set if the adjustment of the value results in a + decimal carry in either digit of the result (see the “Operation” section above). + The SF, ZF, and PF flags are set according to the result. The OF flag is undefined. + */ + const uint8_t old_al = al; + const auto old_carry = status.flag(); + status.set_from(0); + + if((al & 0x0f) > 0x09 || status.flag()) { + status.set_from(old_carry | (al > 0xf9)); + al += 0x06; + status.set_from(1); + } else { + status.set_from(0); + } + + if(old_al > 0x99 || old_carry) { + al += 0x60; + status.set_from(1); + } else { + status.set_from(0); + } + + status.set_from(al); +} + +inline void das(uint8_t &al, Status &status) { + /* + (as modified by https://www.felixcloutier.com/x86/daa ...) + + old_AL ← AL; + old_CF ← CF; + CF ← 0; + + IF (((AL AND 0FH) > 9) or AF = 1) + THEN + AL ← AL - 6; + CF ← old_CF OR CarryFromLastAddition; (* CF OR borrow from AL ← AL - 6 *) + AF ← 1; + ELSE + AF ← 0; + FI; + IF ((old_AL > 99H) or old_CF = 1) + THEN + AL ← AL - 60H; + CF ← 1; + ELSE + CF ← 0; + FI; + */ + /* + The CF and AF flags are set if the adjustment of the value results in a + decimal carry in either digit of the result (see the “Operation” section above). + The SF, ZF, and PF flags are set according to the result. The OF flag is undefined. + */ + const uint8_t old_al = al; + const auto old_carry = status.flag(); + status.set_from(0); + + if((al & 0x0f) > 0x09 || status.flag()) { + status.set_from(old_carry | (al < 0x06)); + al -= 0x06; + status.set_from(1); + } else { + status.set_from(0); + } + + if(old_al > 0x99 || old_carry) { + al -= 0x60; + status.set_from(1); + } else { + status.set_from(0); + } + + status.set_from(al); +} + +template +void add(IntT &destination, IntT source, Status &status) { + /* + DEST ← DEST + SRC [+ CF]; + */ + /* + The OF, SF, ZF, AF, CF, and PF flags are set according to the result. + */ + const IntT result = destination + source + (with_carry ? status.carry_bit() : 0); + + status.set_from( + Numeric::carried_out() - 1>(destination, source, result)); + status.set_from( + Numeric::carried_in<4>(destination, source, result)); + status.set_from( + Numeric::overflow(destination, source, result)); + + status.set_from(result); + + destination = result; +} + +template +void sub(IntT &destination, IntT source, Status &status) { + /* + DEST ← DEST - (SRC [+ CF]); + */ + /* + The OF, SF, ZF, AF, CF, and PF flags are set according to the result. + */ + const IntT result = destination - source - (with_borrow ? status.carry_bit() : 0); + + status.set_from( + Numeric::carried_out() - 1>(destination, source, result)); + status.set_from( + Numeric::carried_in<4>(destination, source, result)); + status.set_from( + Numeric::overflow(destination, source, result)); + + status.set_from(result); + + if constexpr (write_back) { + destination = result; + } +} + +template +void test(IntT &destination, IntT source, Status &status) { + /* + TEMP ← SRC1 AND SRC2; + SF ← MSB(TEMP); + IF TEMP = 0 + THEN ZF ← 0; + ELSE ZF ← 1; + FI: + PF ← BitwiseXNOR(TEMP[0:7]); + CF ← 0; + OF ← 0; + */ + /* + The OF and CF flags are cleared to 0. + The SF, ZF, and PF flags are set according to the result (see the “Operation” section above). + The state of the AF flag is undefined. + */ + const IntT result = destination & source; + + status.set_from(0); + status.set_from(result); +} + +template +void xchg(IntT &destination, IntT &source) { + /* + TEMP ← DEST + DEST ← SRC + SRC ← TEMP + */ + std::swap(destination, source); +} + +template +void mul(IntT &destination_high, IntT &destination_low, IntT source, Status &status) { + /* + IF byte operation + THEN + AX ← AL * SRC + ELSE (* word or doubleword operation *) + IF OperandSize = 16 THEN + DX:AX ← AX * SRC + ELSE (* OperandSize = 32 *) + EDX:EAX ← EAX * SRC + FI; + */ + /* + The OF and CF flags are cleared to 0 if the upper half of the result is 0; + otherwise, they are set to 1. The SF, ZF, AF, and PF flags are undefined. + */ + destination_high = (destination_low * source) >> (8 * sizeof(IntT)); + destination_low *= source; + status.set_from(destination_high); +} + +template +void imul(IntT &destination_high, IntT &destination_low, IntT source, Status &status) { + /* + (as modified by https://www.felixcloutier.com/x86/daa ...) + + IF (OperandSize = 8) + THEN + AX ← AL ∗ SRC (* signed multiplication *) + IF (AX = SignExtend(AL)) + THEN CF = 0; OF = 0; + ELSE CF = 1; OF = 1; + FI; + ELSE IF OperandSize = 16 + THEN + DX:AX ← AX ∗ SRC (* signed multiplication *) + IF (DX:AX = SignExtend(AX)) + THEN CF = 0; OF = 0; + ELSE CF = 1; OF = 1; + FI; + ELSE (* OperandSize = 32 *) + EDX:EAX ← EAX ∗ SRC (* signed multiplication *) + IF (EDX:EAX = SignExtend(EAX)) + THEN CF = 0; OF = 0; + ELSE CF = 1; OF = 1; + FI; + FI; + */ + using sIntT = typename std::make_signed::type; + destination_high = (sIntT(destination_low) * sIntT(source)) >> (8 * sizeof(IntT)); + destination_low = IntT(sIntT(destination_low) * sIntT(source)); + + const auto sign_extension = (destination_low & Numeric::top_bit()) ? IntT(~0) : 0; + status.set_from(destination_high != sign_extension); +} + +template +void div(IntT &destination_high, IntT &destination_low, IntT source, FlowControllerT &flow_controller) { + /* + IF SRC = 0 + THEN #DE; (* divide error *) + FI; + IF OperandSize = 8 (* word/byte operation *) + THEN + temp ← AX / SRC; + IF temp > FFH + THEN #DE; (* divide error *) ; + ELSE + AL ← temp; + AH ← AX MOD SRC; + FI; + ELSE + IF OperandSize = 16 (* doubleword/word operation *) + THEN + temp ← DX:AX / SRC; + IF temp > FFFFH + THEN #DE; (* divide error *) ; + ELSE + AX ← temp; + DX ← DX:AX MOD SRC; + FI; + ELSE (* quadword/doubleword operation *) + temp ← EDX:EAX / SRC; + IF temp > FFFFFFFFH + THEN #DE; (* divide error *) ; + ELSE + EAX ← temp; + EDX ← EDX:EAX MOD SRC; + FI; + FI; + FI; + */ + /* + The CF, OF, SF, ZF, AF, and PF flags are undefined. + */ + if(!source) { + flow_controller.interrupt(Interrupt::DivideError); + return; + } + + // TEMPORARY HACK. Will not work with DWords. + const uint32_t dividend = (destination_high << (8 * sizeof(IntT))) + destination_low; + const auto result = dividend / source; + if(IntT(result) != result) { + flow_controller.interrupt(Interrupt::DivideError); + return; + } + + destination_low = IntT(result); + destination_high = dividend % source; +} + +template +void idiv(IntT &destination_high, IntT &destination_low, IntT source, FlowControllerT &flow_controller) { + /* + IF SRC = 0 + THEN #DE; (* divide error *) + FI; + IF OperandSize = 8 (* word/byte operation *) + THEN + temp ← AX / SRC; (* signed division *) + IF (temp > 7FH) OR (temp < 80H) (* if a positive result is greater than 7FH or a negative result is less than 80H *) + THEN #DE; (* divide error *) ; + ELSE + AL ← temp; + AH ← AX MOD SRC; + FI; + ELSE + IF OperandSize = 16 (* doubleword/word operation *) + THEN + temp ← DX:AX / SRC; (* signed division *) + IF (temp > 7FFFH) OR (temp < 8000H) (* if a positive result is greater than 7FFFH or a negative result is less than 8000H *) + THEN #DE; (* divide error *) ; + ELSE + AX ← temp; + DX ← DX:AX MOD SRC; + FI; + ELSE (* quadword/doubleword operation *) + temp ← EDX:EAX / SRC; (* signed division *) + IF (temp > 7FFFFFFFH) OR (temp < 80000000H) (* if a positive result is greater than 7FFFFFFFH or a negative result is less than 80000000H *) + THEN #DE; (* divide error *) ; + ELSE + EAX ← temp; + EDX ← EDX:EAX MOD SRC; + FI; + FI; + FI; + */ + /* + The CF, OF, SF, ZF, AF, and PF flags are undefined. + */ + if(!source) { + flow_controller.interrupt(Interrupt::DivideError); + return; + } + + // TEMPORARY HACK. Will not work with DWords. + using sIntT = typename std::make_signed::type; + const int32_t dividend = (sIntT(destination_high) << (8 * sizeof(IntT))) + destination_low; + const auto result = dividend / sIntT(source); + if(sIntT(result) != result) { + flow_controller.interrupt(Interrupt::DivideError); + return; + } + + destination_low = IntT(result); + destination_high = dividend % sIntT(source); +} + +template +void inc(IntT &destination, Status &status) { + /* + DEST ← DEST + 1; + */ + /* + The CF flag is not affected. + The OF, SF, ZF, AF, and PF flags are set according to the result. + */ + ++destination; + + status.set_from(destination == Numeric::top_bit()); + status.set_from(((destination - 1) ^ destination) & 0x10); + status.set_from(destination); +} + +template +void jump(bool condition, IntT displacement, RegistersT ®isters, FlowControllerT &flow_controller) { + /* + IF condition + THEN + EIP ← EIP + SignExtend(DEST); + IF OperandSize = 16 + THEN + EIP ← EIP AND 0000FFFFH; + FI; + FI; + */ + + // TODO: proper behaviour in 32-bit. + if(condition) { + flow_controller.jump(registers.ip() + displacement); + } +} + +template +void loop(IntT &counter, OffsetT displacement, RegistersT ®isters, FlowControllerT &flow_controller) { + --counter; + if(counter) { + flow_controller.jump(registers.ip() + displacement); + } +} + +template +void loope(IntT &counter, OffsetT displacement, RegistersT ®isters, Status &status, FlowControllerT &flow_controller) { + --counter; + if(counter && status.flag()) { + flow_controller.jump(registers.ip() + displacement); + } +} + +template +void loopne(IntT &counter, OffsetT displacement, RegistersT ®isters, Status &status, FlowControllerT &flow_controller) { + --counter; + if(counter && !status.flag()) { + flow_controller.jump(registers.ip() + displacement); + } +} + +template +void dec(IntT &destination, Status &status) { + /* + DEST ← DEST - 1; + */ + /* + The CF flag is not affected. + The OF, SF, ZF, AF, and PF flags are set according to the result. + */ + status.set_from(destination == Numeric::top_bit()); + + --destination; + + status.set_from(destination); + status.set_from(((destination + 1) ^ destination) & 0x10); +} + +template +void and_(IntT &destination, IntT source, Status &status) { + /* + DEST ← DEST AND SRC; + */ + /* + The OF and CF flags are cleared; the SF, ZF, and PF flags are set according to the result. + The state of the AF flag is undefined. + */ + destination &= source; + + status.set_from(0); + status.set_from(destination); +} + +template +void or_(IntT &destination, IntT source, Status &status) { + /* + DEST ← DEST OR SRC; + */ + /* + The OF and CF flags are cleared; the SF, ZF, and PF flags are set according to the result. + The state of the AF flag is undefined. + */ + destination |= source; + + status.set_from(0); + status.set_from(destination); +} + +template +void xor_(IntT &destination, IntT source, Status &status) { + /* + DEST ← DEST XOR SRC; + */ + /* + The OF and CF flags are cleared; the SF, ZF, and PF flags are set according to the result. + The state of the AF flag is undefined. + */ + destination ^= source; + + status.set_from(0); + status.set_from(destination); +} + +template +void neg(IntT &destination, Status &status) { + /* + IF DEST = 0 + THEN CF ← 0 + ELSE CF ← 1; + FI; + DEST ← –(DEST) + */ + /* + The CF flag cleared to 0 if the source operand is 0; otherwise it is set to 1. + The OF, SF, ZF, AF, and PF flags are set according to the result. + */ + status.set_from(Numeric::carried_in<4>(IntT(0), destination, IntT(-destination))); + + destination = -destination; + + status.set_from(destination); + status.set_from(destination == Numeric::top_bit()); + status.set_from(destination); +} + +template +void not_(IntT &destination) { + /* + DEST ← NOT DEST; + */ + /* + Flags affected: none. + */ + destination = ~destination; +} + +template +void call_relative(IntT offset, RegistersT ®isters, FlowControllerT &flow_controller) { + flow_controller.call(registers.ip() + offset); +} + +template +void call_absolute(IntT target, FlowControllerT &flow_controller) { + flow_controller.call(target); +} + +template +void jump_absolute(IntT target, FlowControllerT &flow_controller) { + flow_controller.jump(target); +} + +template +void call_far(InstructionT &instruction, + FlowControllerT &flow_controller, + RegistersT ®isters, + MemoryT &memory +) { + // TODO: eliminate 16-bit assumption below. + uint16_t source_address = 0; + const auto pointer = instruction.destination(); + switch(pointer.source()) { + default: + case Source::Immediate: flow_controller.call(instruction.segment(), instruction.offset()); return; + + case Source::Indirect: + source_address = address(instruction, pointer, registers, memory); + break; + case Source::IndirectNoBase: + source_address = address(instruction, pointer, registers, memory); + break; + case Source::DirectAddress: + source_address = address(instruction, pointer, registers, memory); + break; + } + + const Source source_segment = pointer.segment(instruction.segment_override()); + + const uint16_t offset = memory.template access(source_segment, source_address); + source_address += 2; + const uint16_t segment = memory.template access(source_segment, source_address); + flow_controller.call(segment, offset); +} + +template +void jump_far(InstructionT &instruction, + FlowControllerT &flow_controller, + RegistersT ®isters, + MemoryT &memory +) { + // TODO: eliminate 16-bit assumption below. + uint16_t source_address = 0; + const auto pointer = instruction.destination(); + switch(pointer.source()) { + default: + case Source::Immediate: flow_controller.jump(instruction.segment(), instruction.offset()); return; + + case Source::Indirect: + source_address = address(instruction, pointer, registers, memory); + break; + case Source::IndirectNoBase: + source_address = address(instruction, pointer, registers, memory); + break; + case Source::DirectAddress: + source_address = address(instruction, pointer, registers, memory); + break; + } + + const Source source_segment = pointer.segment(instruction.segment_override()); + + const uint16_t offset = memory.template access(source_segment, source_address); + source_address += 2; + const uint16_t segment = memory.template access(source_segment, source_address); + flow_controller.jump(segment, offset); +} + +template +void iret(RegistersT ®isters, FlowControllerT &flow_controller, MemoryT &memory, Status &status) { + // TODO: all modes other than 16-bit real mode. + registers.ip() = pop(memory, registers); + registers.cs() = pop(memory, registers); + status.set(pop(memory, registers)); + flow_controller.did_iret(); +} + +template +void ret_near(InstructionT instruction, RegistersT ®isters, FlowControllerT &flow_controller, MemoryT &memory) { + registers.ip() = pop(memory, registers); + registers.sp() += instruction.operand(); + flow_controller.did_near_ret(); +} + +template +void ret_far(InstructionT instruction, RegistersT ®isters, FlowControllerT &flow_controller, MemoryT &memory) { + registers.ip() = pop(memory, registers); + registers.cs() = pop(memory, registers); + registers.sp() += instruction.operand(); + flow_controller.did_far_ret(); +} + +template +void ld( + InstructionT &instruction, + uint16_t &destination, + MemoryT &memory, + RegistersT ®isters +) { + const auto pointer = instruction.source(); + auto source_address = address(instruction, pointer, registers, memory); + const Source source_segment = pointer.segment(instruction.segment_override()); + + destination = memory.template access(source_segment, source_address); + source_address += 2; + switch(selector) { + case Source::DS: registers.ds() = memory.template access(source_segment, source_address); break; + case Source::ES: registers.es() = memory.template access(source_segment, source_address); break; + } +} + +template +void lea( + const InstructionT &instruction, + IntT &destination, + MemoryT &memory, + RegistersT ®isters +) { + // TODO: address size. + destination = IntT(address(instruction, instruction.source(), registers, memory)); +} + +template +void xlat( + const InstructionT &instruction, + MemoryT &memory, + RegistersT ®isters +) { + Source source_segment = instruction.segment_override(); + if(source_segment == Source::None) source_segment = Source::DS; + + AddressT address; + if constexpr (std::is_same_v) { + address = registers.bx() + registers.al(); + } + + registers.al() = memory.template access(source_segment, address); +} + +template +void mov(IntT &destination, IntT source) { + destination = source; +} + +template +void int_(uint8_t vector, FlowControllerT &flow_controller) { + flow_controller.interrupt(vector); +} + +template +void into(Status &status, FlowControllerT &flow_controller) { + if(status.flag()) { + flow_controller.interrupt(Interrupt::OnOverflow); + } +} + +inline void sahf(uint8_t &ah, Status &status) { + /* + EFLAGS(SF:ZF:0:AF:0:PF:1:CF) ← AH; + */ + status.set_from(ah); + status.set_from(!(ah & 0x40)); + status.set_from(ah & 0x10); + status.set_from(!(ah & 0x04)); + status.set_from(ah & 0x01); +} + +inline void lahf(uint8_t &ah, Status &status) { + /* + AH ← EFLAGS(SF:ZF:0:AF:0:PF:1:CF); + */ + ah = + (status.flag() ? 0x80 : 0x00) | + (status.flag() ? 0x40 : 0x00) | + (status.flag() ? 0x10 : 0x00) | + (status.flag() ? 0x00 : 0x04) | + 0x02 | + (status.flag() ? 0x01 : 0x00); +} + +template +void cbw(IntT &ax) { + constexpr IntT test_bit = 1 << (sizeof(IntT) * 4 - 1); + constexpr IntT low_half = (1 << (sizeof(IntT) * 4)) - 1; + + if(ax & test_bit) { + ax |= ~low_half; + } else { + ax &= low_half; + } +} + +template +void cwd(IntT &dx, IntT ax) { + dx = ax & Numeric::top_bit() ? IntT(~0) : IntT(0); +} + +// TODO: changes to the interrupt flag do quite a lot more in protected mode. +inline void clc(Status &status) { status.set_from(0); } +inline void cld(Status &status) { status.set_from(0); } +inline void cli(Status &status) { status.set_from(0); } +inline void stc(Status &status) { status.set_from(1); } +inline void std(Status &status) { status.set_from(1); } +inline void sti(Status &status) { status.set_from(1); } +inline void cmc(Status &status) { status.set_from(!status.flag()); } + +inline void salc(uint8_t &al, const Status &status) { + al = status.flag() ? 0xff : 0x00; +} + +template +void setmo(IntT &destination, Status &status) { + destination = ~0; + status.set_from(0); + status.set_from(destination); +} + +template +void setmoc(IntT &destination, uint8_t cl, Status &status) { + if(cl) setmo(destination, status); +} + +template +inline void rcl(IntT &destination, uint8_t count, Status &status) { + /* + (* RCL and RCR instructions *) + SIZE ← OperandSize + CASE (determine count) OF + SIZE = 8: tempCOUNT ← (COUNT AND 1FH) MOD 9; + SIZE = 16: tempCOUNT ← (COUNT AND 1FH) MOD 17; + SIZE = 32: tempCOUNT ← COUNT AND 1FH; + ESAC; + */ + /* + (* RCL instruction operation *) + WHILE (tempCOUNT ≠ 0) + DO + tempCF ← MSB(DEST); + DEST ← (DEST * 2) + CF; + CF ← tempCF; + tempCOUNT ← tempCOUNT – 1; + OD; + ELIHW; + IF COUNT = 1 + THEN OF ← MSB(DEST) XOR CF; + ELSE OF is undefined; + FI; + */ + /* + The CF flag contains the value of the bit shifted into it. + The OF flag is affected only for single- bit rotates (see “Description” above); + it is undefined for multi-bit rotates. The SF, ZF, AF, and PF flags are not affected. + */ + const auto temp_count = count % (Numeric::bit_size() + 1); + auto carry = status.carry_bit(); + switch(temp_count) { + case 0: break; + case Numeric::bit_size(): { + const IntT temp_carry = destination & 1; + destination = (destination >> 1) | (carry << (Numeric::bit_size() - 1)); + carry = temp_carry; + } break; + default: { + const IntT temp_carry = destination & (Numeric::top_bit() >> (temp_count - 1)); + destination = + (destination << temp_count) | + (destination >> (Numeric::bit_size() + 1 - temp_count)) | + (carry << (temp_count - 1)); + carry = temp_carry ? 1 : 0; + } break; + } + + status.set_from(carry); + status.set_from( + ((destination >> (Numeric::bit_size() - 1)) & 1) ^ carry + ); +} + +template +inline void rcr(IntT &destination, uint8_t count, Status &status) { + /* + (* RCR instruction operation *) + IF COUNT = 1 + THEN OF ← MSB(DEST) XOR CF; + ELSE OF is undefined; + FI; + WHILE (tempCOUNT ≠ 0) + DO + tempCF ← LSB(SRC); + DEST ← (DEST / 2) + (CF * 2SIZE); + CF ← tempCF; + tempCOUNT ← tempCOUNT – 1; + OD; + */ + auto carry = status.carry_bit(); + status.set_from( + ((destination >> (Numeric::bit_size() - 1)) & 1) ^ carry + ); + + const auto temp_count = count % (Numeric::bit_size() + 1); + switch(temp_count) { + case 0: break; + case Numeric::bit_size(): { + const IntT temp_carry = destination & Numeric::top_bit(); + destination = (destination << 1) | carry; + carry = temp_carry; + } break; + default: { + const IntT temp_carry = destination & (1 << (temp_count - 1)); + destination = + (destination >> temp_count) | + (destination << (Numeric::bit_size() + 1 - temp_count)) | + (carry << (Numeric::bit_size() - temp_count)); + carry = temp_carry; + } break; + } + + status.set_from(carry); +} + +template +inline void rol(IntT &destination, uint8_t count, Status &status) { + /* + (* ROL and ROR instructions *) + SIZE ← OperandSize + CASE (determine count) OF + SIZE = 8: tempCOUNT ← COUNT MOD 8; + SIZE = 16: tempCOUNT ← COUNT MOD 16; + SIZE = 32: tempCOUNT ← COUNT MOD 32; + ESAC; + */ + /* + (* ROL instruction operation *) + WHILE (tempCOUNT ≠ 0) + DO + tempCF ← MSB(DEST); + DEST ← (DEST * 2) + tempCF; + tempCOUNT ← tempCOUNT – 1; + OD; + ELIHW; + IF COUNT = 1 + THEN OF ← MSB(DEST) XOR CF; + ELSE OF is undefined; + FI; + */ + /* + The CF flag contains the value of the bit shifted into it. + The OF flag is affected only for single- bit rotates (see “Description” above); + it is undefined for multi-bit rotates. The SF, ZF, AF, and PF flags are not affected. + */ + const auto temp_count = count & (Numeric::bit_size() - 1); + if(!count) { + // TODO: is this 8086-specific? i.e. do the other x86s also exit without affecting flags when temp_count = 0? + return; + } + if(temp_count) { + destination = + (destination << temp_count) | + (destination >> (Numeric::bit_size() - temp_count)); + } + + status.set_from(destination & 1); + status.set_from( + ((destination >> (Numeric::bit_size() - 1)) ^ destination) & 1 + ); +} + +template +inline void ror(IntT &destination, uint8_t count, Status &status) { + /* + (* ROL and ROR instructions *) + SIZE ← OperandSize + CASE (determine count) OF + SIZE = 8: tempCOUNT ← COUNT MOD 8; + SIZE = 16: tempCOUNT ← COUNT MOD 16; + SIZE = 32: tempCOUNT ← COUNT MOD 32; + ESAC; + */ + /* + (* ROR instruction operation *) + WHILE (tempCOUNT ≠ 0) + DO + tempCF ← LSB(DEST); + DEST ← (DEST / 2) + (tempCF * 2^SIZE); + tempCOUNT ← tempCOUNT – 1; + OD; + ELIHW; + IF COUNT = 1 + THEN OF ← MSB(DEST) XOR MSB - 1 (DEST); + ELSE OF is undefined; + FI; + */ + /* + The CF flag contains the value of the bit shifted into it. + The OF flag is affected only for single- bit rotates (see “Description” above); + it is undefined for multi-bit rotates. The SF, ZF, AF, and PF flags are not affected. + */ + const auto temp_count = count & (Numeric::bit_size() - 1); + if(!count) { + // TODO: is this 8086-specific? i.e. do the other x86s also exit without affecting flags when temp_count = 0? + return; + } + if(temp_count) { + destination = + (destination >> temp_count) | + (destination << (Numeric::bit_size() - temp_count)); + } + + status.set_from(destination & Numeric::top_bit()); + status.set_from( + (destination ^ (destination << 1)) & Numeric::top_bit() + ); +} + +/* + tempCOUNT ← (COUNT AND 1FH); + tempDEST ← DEST; + WHILE (tempCOUNT ≠ 0) + DO + IF instruction is SAL or SHL + THEN + CF ← MSB(DEST); + ELSE (* instruction is SAR or SHR *) + CF ← LSB(DEST); + FI; + IF instruction is SAL or SHL + THEN + DEST ← DEST ∗ 2; + ELSE + IF instruction is SAR + THEN + DEST ← DEST / 2 (*Signed divide, rounding toward negative infinity*); + ELSE (* instruction is SHR *) + DEST ← DEST / 2 ; (* Unsigned divide *); + FI; + FI; + tempCOUNT ← tempCOUNT – 1; + OD; + (* Determine overflow for the various instructions *) + IF COUNT = 1 + THEN + IF instruction is SAL or SHL + THEN + OF ← MSB(DEST) XOR CF; + ELSE + IF instruction is SAR + THEN + OF ← 0; + ELSE (* instruction is SHR *) + OF ← MSB(tempDEST); + FI; + FI; + ELSE + IF COUNT = 0 + THEN + All flags remain unchanged; + ELSE (* COUNT neither 1 or 0 *) + OF ← undefined; + FI; + FI; +*/ +/* + The CF flag contains the value of the last bit shifted out of the destination operand; + it is undefined for SHL and SHR instructions where the count is greater than or equal to + the size (in bits) of the destination operand. The OF flag is affected only for 1-bit shifts + (see “Description” above); otherwise, it is undefined. + + The SF, ZF, and PF flags are set according to the result. If the count is 0, the flags are not affected. + For a non-zero count, the AF flag is undefined. +*/ +template +inline void sal(IntT &destination, uint8_t count, Status &status) { + switch(count) { + case 0: return; + case Numeric::bit_size(): + status.set_from(destination & 1); + destination = 0; + break; + default: + if(count > Numeric::bit_size()) { + status.set_from(0); + destination = 0; + } else { + const auto mask = (Numeric::top_bit() >> (count - 1)); + status.set_from( + destination & mask + ); + status.set_from( + (destination ^ (destination << 1)) & mask + ); + destination <<= count; + } + break; + } + status.set_from(destination); +} + +template +inline void sar(IntT &destination, uint8_t count, Status &status) { + if(!count) { + return; + } + + const IntT sign = Numeric::top_bit() & destination; + if(count >= Numeric::bit_size()) { + destination = sign ? IntT(~0) : IntT(0); + status.set_from(sign); + } else { + const IntT mask = 1 << (count - 1); + status.set_from(destination & mask); + destination = (destination >> count) | (sign ? ~(IntT(~0) >> count) : 0); + } + status.set_from(0); + status.set_from(destination); +} + +template +inline void shr(IntT &destination, uint8_t count, Status &status) { + if(!count) { + return; + } + + status.set_from(Numeric::top_bit() & destination); + if(count == Numeric::bit_size()) { + status.set_from(Numeric::top_bit() & destination); + destination = 0; + } else if(count > Numeric::bit_size()) { + status.set_from(0); + destination = 0; + } else { + const IntT mask = 1 << (count - 1); + status.set_from(destination & mask); + destination >>= count; + } + status.set_from(destination); +} + +template +void popf(MemoryT &memory, RegistersT ®isters, Status &status) { + status.set(pop(memory, registers)); +} + +template +void pushf(MemoryT &memory, RegistersT ®isters, Status &status) { + uint16_t value = status.get(); + push(value, memory, registers); +} + +template +bool repetition_over(const InstructionT &instruction, AddressT &eCX) { + return instruction.repetition() != Repetition::None && !eCX; +} + +template +void repeat_ene(const InstructionT &instruction, Status &status, AddressT &eCX, FlowControllerT &flow_controller) { + if( + instruction.repetition() == Repetition::None || // No repetition => stop. + !(--eCX) || // [e]cx is zero after being decremented => stop. + (instruction.repetition() == Repetition::RepNE) == status.flag() + // repe and !zero, or repne and zero => stop. + ) { + return; + } + flow_controller.repeat_last(); +} + +template +void repeat(const InstructionT &instruction, AddressT &eCX, FlowControllerT &flow_controller) { + if( + instruction.repetition() == Repetition::None || // No repetition => stop. + !(--eCX) // [e]cx is zero after being decremented => stop. + ) { + return; + } + flow_controller.repeat_last(); +} + +template +void cmps(const InstructionT &instruction, AddressT &eCX, AddressT &eSI, AddressT &eDI, MemoryT &memory, Status &status, FlowControllerT &flow_controller) { + if(repetition_over(instruction, eCX)) { + return; + } + + Source source_segment = instruction.segment_override(); + if(source_segment == Source::None) source_segment = Source::DS; + + IntT lhs = memory.template access(source_segment, eSI); + const IntT rhs = memory.template access(Source::ES, eDI); + eSI += status.direction() * sizeof(IntT); + eDI += status.direction() * sizeof(IntT); + + Primitive::sub(lhs, rhs, status); + + repeat_ene(instruction, status, eCX, flow_controller); +} + +template +void scas(const InstructionT &instruction, AddressT &eCX, AddressT &eDI, IntT &eAX, MemoryT &memory, Status &status, FlowControllerT &flow_controller) { + if(repetition_over(instruction, eCX)) { + return; + } + + const IntT rhs = memory.template access(Source::ES, eDI); + eDI += status.direction() * sizeof(IntT); + + Primitive::sub(eAX, rhs, status); + + repeat_ene(instruction, status, eCX, flow_controller); +} + +template +void lods(const InstructionT &instruction, AddressT &eCX, AddressT &eSI, IntT &eAX, MemoryT &memory, Status &status, FlowControllerT &flow_controller) { + if(repetition_over(instruction, eCX)) { + return; + } + + Source source_segment = instruction.segment_override(); + if(source_segment == Source::None) source_segment = Source::DS; + + eAX = memory.template access(source_segment, eSI); + eSI += status.direction() * sizeof(IntT); + + repeat(instruction, eCX, flow_controller); +} + +template +void movs(const InstructionT &instruction, AddressT &eCX, AddressT &eSI, AddressT &eDI, MemoryT &memory, Status &status, FlowControllerT &flow_controller) { + if(repetition_over(instruction, eCX)) { + return; + } + + Source source_segment = instruction.segment_override(); + if(source_segment == Source::None) source_segment = Source::DS; + + memory.template access(Source::ES, eDI) = memory.template access(source_segment, eSI); + + eSI += status.direction() * sizeof(IntT); + eDI += status.direction() * sizeof(IntT); + + repeat(instruction, eCX, flow_controller); +} + +template +void stos(const InstructionT &instruction, AddressT &eCX, AddressT &eDI, IntT &eAX, MemoryT &memory, Status &status, FlowControllerT &flow_controller) { + if(repetition_over(instruction, eCX)) { + return; + } + + memory.template access(Source::ES, eDI) = eAX; + eDI += status.direction() * sizeof(IntT); + + repeat(instruction, eCX, flow_controller); +} + +template +void outs(const InstructionT &instruction, AddressT &eCX, uint16_t port, AddressT &eSI, MemoryT &memory, IOT &io, Status &status, FlowControllerT &flow_controller) { + if(repetition_over(instruction, eCX)) { + return; + } + + Source source_segment = instruction.segment_override(); + if(source_segment == Source::None) source_segment = Source::DS; + io.template out(port, memory.template access(source_segment, eSI)); + eSI += status.direction() * sizeof(IntT); + + repeat(instruction, eCX, flow_controller); +} + +template +void ins(const InstructionT &instruction, AddressT &eCX, uint16_t port, AddressT &eDI, MemoryT &memory, IOT &io, Status &status, FlowControllerT &flow_controller) { + if(repetition_over(instruction, eCX)) { + return; + } + + memory.template access(Source::ES, eDI) = io.template in(port); + eDI += status.direction() * sizeof(IntT); + + repeat(instruction, eCX, flow_controller); +} + +template +void out(uint16_t port, IntT value, IOT &io) { + io.template out(port, value); +} + +template +void in(uint16_t port, IntT &value, IOT &io) { + value = io.template in(port); +} + +} + +template < + Model model, + DataSize data_size, + AddressSize address_size, + typename InstructionT, + typename FlowControllerT, + typename RegistersT, + typename MemoryT, + typename IOT +> void perform( + const InstructionT &instruction, + Status &status, + FlowControllerT &flow_controller, + RegistersT ®isters, + MemoryT &memory, + IOT &io +) { + using IntT = typename DataSizeType::type; + using AddressT = typename AddressSizeType::type; + + // Establish source() and destination() shorthand to fetch data if necessary. + IntT immediate; + const auto source = [&]() -> IntT& { + return *resolve( + instruction, + instruction.source().source(), + instruction.source(), + registers, + memory, + nullptr, + &immediate); + }; + const auto destination = [&]() -> IntT& { + return *resolve( + instruction, + instruction.destination().source(), + instruction.destination(), + registers, + memory, + nullptr, + &immediate); + }; + + // Performs a displacement jump only if @c condition is true. + const auto jcc = [&](bool condition) { + Primitive::jump( + condition, + instruction.displacement(), + registers, + flow_controller); + }; + + const auto shift_count = [&]() -> uint8_t { + static constexpr uint8_t mask = (model != Model::i8086) ? 0x1f : 0xff; + switch(instruction.source().source()) { + case Source::None: return 1; + case Source::Immediate: return uint8_t(instruction.operand()) & mask; + default: return registers.cl() & mask; + } + }; + + // Some instructions use a pair of registers as an extended accumulator — DX:AX or EDX:EAX. + // The two following return the high and low parts of that pair; they also work in Byte mode to return AH:AL, + // i.e. AX split into high and low parts. + const auto pair_high = [&]() -> IntT& { + if constexpr (data_size == DataSize::Byte) return registers.ah(); + else if constexpr (data_size == DataSize::Word) return registers.dx(); + else if constexpr (data_size == DataSize::DWord) return registers.edx(); + }; + const auto pair_low = [&]() -> IntT& { + if constexpr (data_size == DataSize::Byte) return registers.al(); + else if constexpr (data_size == DataSize::Word) return registers.ax(); + else if constexpr (data_size == DataSize::DWord) return registers.eax(); + }; + + // For the string operations, evaluate to either SI and DI or ESI and EDI, depending on the address size. + const auto eSI = [&]() -> AddressT& { + if constexpr (std::is_same_v) { + return registers.si(); + } else { + return registers.esi(); + } + }; + const auto eDI = [&]() -> AddressT& { + if constexpr (std::is_same_v) { + return registers.di(); + } else { + return registers.edi(); + } + }; + + // For counts, provide either eCX or CX depending on address size. + const auto eCX = [&]() -> AddressT& { + if constexpr (std::is_same_v) { + return registers.cx(); + } else { + return registers.ecx(); + } + }; + + // Gets the port for an IN or OUT; these are always 16-bit. + const auto port = [&](Source source) -> uint16_t { + switch(source) { + case Source::DirectAddress: return instruction.operand(); + default: return registers.dx(); + } + }; + + // Guide to the below: + // + // * use hard-coded register names where appropriate; + // * return directly if there is definitely no possible write back to RAM; + // * otherwise use the source() and destination() lambdas, and break in order to allow a writeback if necessary. + switch(instruction.operation) { + default: + assert(false); + + case Operation::AAA: Primitive::aaa(registers.axp(), status); return; + case Operation::AAD: Primitive::aad(registers.axp(), instruction.operand(), status); return; + case Operation::AAM: Primitive::aam(registers.axp(), instruction.operand(), status, flow_controller); return; + case Operation::AAS: Primitive::aas(registers.axp(), status); return; + case Operation::DAA: Primitive::daa(registers.al(), status); return; + case Operation::DAS: Primitive::das(registers.al(), status); return; + + case Operation::CBW: Primitive::cbw(pair_low()); return; + case Operation::CWD: Primitive::cwd(pair_high(), pair_low()); return; + + case Operation::ESC: + case Operation::NOP: return; + + case Operation::HLT: flow_controller.halt(); return; + case Operation::WAIT: flow_controller.wait(); return; + + case Operation::ADC: Primitive::add(destination(), source(), status); break; + case Operation::ADD: Primitive::add(destination(), source(), status); break; + case Operation::SBB: Primitive::sub(destination(), source(), status); break; + case Operation::SUB: Primitive::sub(destination(), source(), status); break; + case Operation::CMP: Primitive::sub(destination(), source(), status); break; + case Operation::TEST: Primitive::test(destination(), source(), status); break; + + case Operation::MUL: Primitive::mul(pair_high(), pair_low(), source(), status); return; + case Operation::IMUL_1: Primitive::imul(pair_high(), pair_low(), source(), status); return; + case Operation::DIV: Primitive::div(pair_high(), pair_low(), source(), flow_controller); return; + case Operation::IDIV: Primitive::idiv(pair_high(), pair_low(), source(), flow_controller); return; + + case Operation::INC: Primitive::inc(destination(), status); break; + case Operation::DEC: Primitive::dec(destination(), status); break; + + case Operation::AND: Primitive::and_(destination(), source(), status); break; + case Operation::OR: Primitive::or_(destination(), source(), status); break; + case Operation::XOR: Primitive::xor_(destination(), source(), status); break; + case Operation::NEG: Primitive::neg(source(), status); break; + case Operation::NOT: Primitive::not_(source()); break; + + case Operation::CALLrel: + Primitive::call_relative(instruction.displacement(), registers, flow_controller); + return; + case Operation::CALLabs: + Primitive::call_absolute(destination(), flow_controller); + return; + case Operation::CALLfar: + Primitive::call_far(instruction, flow_controller, registers, memory); + return; + + case Operation::JMPrel: jcc(true); return; + case Operation::JMPabs: Primitive::jump_absolute(destination(), flow_controller); return; + case Operation::JMPfar: Primitive::jump_far(instruction, flow_controller, registers, memory); return; + + case Operation::JCXZ: jcc(!eCX()); return; + case Operation::LOOP: Primitive::loop(eCX(), instruction.offset(), registers, flow_controller); return; + case Operation::LOOPE: Primitive::loope(eCX(), instruction.offset(), registers, status, flow_controller); return; + case Operation::LOOPNE: Primitive::loopne(eCX(), instruction.offset(), registers, status, flow_controller); return; + + case Operation::IRET: Primitive::iret(registers, flow_controller, memory, status); return; + case Operation::RETnear: Primitive::ret_near(instruction, registers, flow_controller, memory); return; + case Operation::RETfar: Primitive::ret_far(instruction, registers, flow_controller, memory); return; + + case Operation::INT: Primitive::int_(instruction.operand(), flow_controller); return; + case Operation::INTO: Primitive::into(status, flow_controller); return; + + case Operation::SAHF: Primitive::sahf(registers.ah(), status); return; + case Operation::LAHF: Primitive::lahf(registers.ah(), status); return; + + case Operation::LDS: if constexpr (data_size == DataSize::Word) Primitive::ld(instruction, destination(), memory, registers); return; + case Operation::LES: if constexpr (data_size == DataSize::Word) Primitive::ld(instruction, destination(), memory, registers); return; + + case Operation::LEA: Primitive::lea(instruction, destination(), memory, registers); return; + case Operation::MOV: Primitive::mov(destination(), source()); return; + + case Operation::JO: jcc(status.condition()); return; + case Operation::JNO: jcc(!status.condition()); return; + case Operation::JB: jcc(status.condition()); return; + case Operation::JNB: jcc(!status.condition()); return; + case Operation::JZ: jcc(status.condition()); return; + case Operation::JNZ: jcc(!status.condition()); return; + case Operation::JBE: jcc(status.condition()); return; + case Operation::JNBE: jcc(!status.condition()); return; + case Operation::JS: jcc(status.condition()); return; + case Operation::JNS: jcc(!status.condition()); return; + case Operation::JP: jcc(!status.condition()); return; + case Operation::JNP: jcc(status.condition()); return; + case Operation::JL: jcc(status.condition()); return; + case Operation::JNL: jcc(!status.condition()); return; + case Operation::JLE: jcc(status.condition()); return; + case Operation::JNLE: jcc(!status.condition()); return; + + case Operation::RCL: Primitive::rcl(destination(), shift_count(), status); break; + case Operation::RCR: Primitive::rcr(destination(), shift_count(), status); break; + case Operation::ROL: Primitive::rol(destination(), shift_count(), status); break; + case Operation::ROR: Primitive::ror(destination(), shift_count(), status); break; + case Operation::SAL: Primitive::sal(destination(), shift_count(), status); break; + case Operation::SAR: Primitive::sar(destination(), shift_count(), status); break; + case Operation::SHR: Primitive::shr(destination(), shift_count(), status); break; + + case Operation::CLC: Primitive::clc(status); return; + case Operation::CLD: Primitive::cld(status); return; + case Operation::CLI: Primitive::cli(status); return; + case Operation::STC: Primitive::stc(status); return; + case Operation::STD: Primitive::std(status); return; + case Operation::STI: Primitive::sti(status); return; + case Operation::CMC: Primitive::cmc(status); return; + + case Operation::XCHG: Primitive::xchg(destination(), source()); return; + + case Operation::SALC: Primitive::salc(registers.al(), status); return; + case Operation::SETMO: + if constexpr (model == Model::i8086) { + Primitive::setmo(destination(), status); + } else { + // TODO. + } + return; + case Operation::SETMOC: + if constexpr (model == Model::i8086) { + Primitive::setmoc(destination(), registers.cl(), status); + } else { + // TODO. + } + return; + + case Operation::OUT: Primitive::out(port(instruction.destination().source()), pair_low(), io); return; + case Operation::IN: Primitive::in(port(instruction.source().source()), pair_low(), io); return; + + case Operation::XLAT: Primitive::xlat(instruction, memory, registers); return; + + case Operation::POP: source() = Primitive::pop(memory, registers); break; + case Operation::PUSH: Primitive::push(source(), memory, registers); break; + case Operation::POPF: Primitive::popf(memory, registers, status); break; + case Operation::PUSHF: Primitive::pushf(memory, registers, status); break; + + case Operation::CMPS: + Primitive::cmps(instruction, eCX(), eSI(), eDI(), memory, status, flow_controller); + break; + case Operation::LODS: + Primitive::lods(instruction, eCX(), eSI(), pair_low(), memory, status, flow_controller); + break; + case Operation::MOVS: + Primitive::movs(instruction, eCX(), eSI(), eDI(), memory, status, flow_controller); + break; + case Operation::STOS: + Primitive::stos(instruction, eCX(), eDI(), pair_low(), memory, status, flow_controller); + break; + case Operation::SCAS: + Primitive::scas(instruction, eCX(), eDI(), pair_low(), memory, status, flow_controller); + break; + case Operation::OUTS: + Primitive::outs(instruction, eCX(), registers.dx(), eSI(), memory, io, status, flow_controller); + break; + case Operation::INS: + Primitive::outs(instruction, eCX(), registers.dx(), eDI(), memory, io, status, flow_controller); + break; + } + + // Write to memory if required to complete this operation. + memory.template write_back(); +} + +template < + Model model, + typename InstructionT, + typename FlowControllerT, + typename RegistersT, + typename MemoryT, + typename IOT +> void perform( + const InstructionT &instruction, + Status &status, + FlowControllerT &flow_controller, + RegistersT ®isters, + MemoryT &memory, + IOT &io +) { + auto size = [](DataSize operation_size, AddressSize address_size) constexpr -> int { + return int(operation_size) + (int(address_size) << 2); + }; + + // Dispatch to a function specialised on data and address size. + switch(size(instruction.operation_size(), instruction.address_size())) { + // 16-bit combinations. + case size(DataSize::Byte, AddressSize::b16): + perform(instruction, status, flow_controller, registers, memory, io); + return; + case size(DataSize::Word, AddressSize::b16): + perform(instruction, status, flow_controller, registers, memory, io); + return; + + // 32-bit combinations. + // + // The if constexprs below ensure that `perform` isn't compiled for incompatible data or address size and + // model combinations. So if a caller nominates a 16-bit model it can supply registers and memory objects + // that don't implement 32-bit registers or accesses. + case size(DataSize::Byte, AddressSize::b32): + if constexpr (is_32bit(model)) { + perform(instruction, status, flow_controller, registers, memory, io); + return; + } + break; + case size(DataSize::Word, AddressSize::b32): + if constexpr (is_32bit(model)) { + perform(instruction, status, flow_controller, registers, memory, io); + return; + } + break; + case size(DataSize::DWord, AddressSize::b16): + if constexpr (is_32bit(model)) { + perform(instruction, status, flow_controller, registers, memory, io); + return; + } + break; + case size(DataSize::DWord, AddressSize::b32): + if constexpr (is_32bit(model)) { + perform(instruction, status, flow_controller, registers, memory, io); + return; + } + break; + + default: break; + } + + // This is reachable only if the data and address size combination in use isn't available + // on the processor model nominated. + assert(false); +} + +} + +#endif /* PerformImplementation_h */ diff --git a/InstructionSets/x86/Instruction.cpp b/InstructionSets/x86/Instruction.cpp index c247a4f11..91a8b5e31 100644 --- a/InstructionSets/x86/Instruction.cpp +++ b/InstructionSets/x86/Instruction.cpp @@ -8,6 +8,8 @@ #include "Instruction.hpp" +#include "../../Numeric/Carry.hpp" + #include #include #include @@ -321,6 +323,13 @@ std::string to_hex(int value, int digits, bool with_suffix = true) { return stream.str(); }; +template +std::string to_hex(IntT value) { + auto stream = std::stringstream(); + stream << std::uppercase << std::hex << +value << 'h'; + return stream.str(); +}; + } template @@ -335,42 +344,42 @@ std::string InstructionSet::x86::to_string( std::string operand; - auto append = [](std::stringstream &stream, auto value, int length, const char *prefix) { + auto append = [](std::stringstream &stream, auto value, int length) { switch(length) { case 0: if(!value) { - break; + return; } [[fallthrough]]; + case 2: - // If asked to pretend the offset was originally two digits then either of: an unsigned - // 8-bit value or a sign-extended 8-bit value as having been originally 8-bit. - // - // This kicks the issue of whether sign was extended appropriately to functionality tests. - if( - !(value & 0xff00) || - ((value & 0xff80) == 0xff80) || - ((value & 0xff80) == 0x0000) - ) { - stream << prefix << to_hex(value, 2); - break; - } - [[fallthrough]]; - default: - stream << prefix << to_hex(value, 4); - break; + value &= 0xff; + break; } + + stream << std::uppercase << std::hex << value << 'h'; + }; + + auto append_signed = [](std::stringstream &stream, auto value, int length) { + if(!value && !length) { + return; + } + + const bool is_negative = Numeric::top_bit() & value; + const uint64_t abs_value = std::abs(int16_t(value)); // TODO: don't assume 16-bit. + + stream << (is_negative ? '-' : '+') << std::uppercase << std::hex << abs_value << 'h'; }; using Source = InstructionSet::x86::Source; - const Source source = pointer.source(); + const Source source = pointer.source(); switch(source) { // to_string handles all direct register names correctly. default: return InstructionSet::x86::to_string(source, operation_size); case Source::Immediate: { std::stringstream stream; - append(stream, instruction.operand(), immediate_length, ""); + append(stream, instruction.operand(), immediate_length); return stream.str(); } @@ -383,7 +392,8 @@ std::string InstructionSet::x86::to_string( stream << InstructionSet::x86::to_string(operation_size) << ' '; } - Source segment = instruction.data_segment(); + stream << '['; + Source segment = instruction.segment_override(); if(segment == Source::None) { segment = pointer.default_segment(); if(segment == Source::None) { @@ -392,7 +402,6 @@ std::string InstructionSet::x86::to_string( } stream << InstructionSet::x86::to_string(segment, InstructionSet::x86::DataSize::None) << ':'; - stream << '['; bool addOffset = false; switch(source) { default: break; @@ -408,11 +417,11 @@ std::string InstructionSet::x86::to_string( addOffset = true; break; case Source::DirectAddress: - stream << to_hex(instruction.offset(), 4); + stream << std::uppercase << std::hex << instruction.offset() << 'h'; break; } if(addOffset) { - append(stream, instruction.offset(), offset_length, "+"); + append_signed(stream, instruction.offset(), offset_length); } stream << ']'; return stream.str(); @@ -424,67 +433,96 @@ std::string InstructionSet::x86::to_string( template std::string InstructionSet::x86::to_string( - Instruction instruction, + std::pair> instruction, Model model, int offset_length, int immediate_length ) { std::string operation; + // Add segment override, if any, ahead of some operations that won't otherwise print it. + switch(instruction.second.operation) { + default: break; + + case Operation::CMPS: + case Operation::SCAS: + case Operation::STOS: + case Operation::LODS: + case Operation::MOVS: + switch(instruction.second.segment_override()) { + default: break; + case Source::ES: operation += "es "; break; + case Source::CS: operation += "cs "; break; + case Source::DS: operation += "ds "; break; + case Source::SS: operation += "ss "; break; + case Source::GS: operation += "gs "; break; + case Source::FS: operation += "fs "; break; + } + break; + } + // Add a repetition prefix; it'll be one of 'rep', 'repe' or 'repne'. - switch(instruction.repetition()) { + switch(instruction.second.repetition()) { case Repetition::None: break; case Repetition::RepE: - switch(instruction.operation) { - default: + switch(instruction.second.operation) { + case Operation::CMPS: + case Operation::SCAS: operation += "repe "; break; - case Operation::MOVS: - case Operation::STOS: - case Operation::LODS: + default: operation += "rep "; break; } break; case Repetition::RepNE: - operation += "repne "; + switch(instruction.second.operation) { + case Operation::CMPS: + case Operation::SCAS: + operation += "repne "; + break; + + default: + operation += "rep "; + break; + } break; } // Add operation itself. - operation += to_string(instruction.operation, instruction.operation_size(), model); + operation += to_string(instruction.second.operation, instruction.second.operation_size(), model); operation += " "; // Deal with a few special cases up front. - switch(instruction.operation) { + switch(instruction.second.operation) { default: { - const int operands = max_displayed_operands(instruction.operation); - const bool displacement = has_displacement(instruction.operation); - const bool print_first = operands > 1 && instruction.destination().source() != Source::None; + const int operands = max_displayed_operands(instruction.second.operation); + const bool displacement = has_displacement(instruction.second.operation); + const bool print_first = operands > 1 && instruction.second.destination().source() != Source::None; if(print_first) { - operation += to_string(instruction.destination(), instruction, offset_length, immediate_length); + operation += to_string(instruction.second.destination(), instruction.second, offset_length, immediate_length); } - if(operands > 0 && instruction.source().source() != Source::None) { + if(operands > 0 && instruction.second.source().source() != Source::None) { if(print_first) operation += ", "; - operation += to_string(instruction.source(), instruction, offset_length, immediate_length); + operation += to_string(instruction.second.source(), instruction.second, offset_length, immediate_length); } if(displacement) { - operation += to_hex(instruction.displacement(), offset_length); + operation += to_hex(instruction.second.displacement() + instruction.first, offset_length); } } break; case Operation::CALLfar: case Operation::JMPfar: { - switch(instruction.destination().source()) { + switch(instruction.second.destination().source()) { case Source::Immediate: - operation += "far 0x"; - operation += to_hex(instruction.segment(), 4, false); - operation += ":0x"; - operation += to_hex(instruction.offset(), 4, false); + operation += to_hex(instruction.second.segment(), 4, false); + operation += "h:"; + operation += to_hex(instruction.second.offset(), 4, false); + operation += "h"; break; default: - operation += to_string(instruction.destination(), instruction, offset_length, immediate_length); + operation += to_string(instruction.second.destination(), instruction.second, offset_length, immediate_length); break; } } break; @@ -492,35 +530,35 @@ std::string InstructionSet::x86::to_string( case Operation::LDS: case Operation::LES: // The test set labels the pointer type as dword, which I guess is technically accurate. // A full 32 bits will be loaded from that address in 16-bit mode. - operation += to_string(instruction.destination(), instruction, offset_length, immediate_length); + operation += to_string(instruction.second.destination(), instruction.second, offset_length, immediate_length); operation += ", "; - operation += to_string(instruction.source(), instruction, offset_length, immediate_length, InstructionSet::x86::DataSize::DWord); + operation += to_string(instruction.second.source(), instruction.second, offset_length, immediate_length, InstructionSet::x86::DataSize::DWord); break; case Operation::IN: - operation += to_string(instruction.destination(), instruction, offset_length, immediate_length); + operation += to_string(instruction.second.destination(), instruction.second, offset_length, immediate_length); operation += ", "; - switch(instruction.source().source()) { + switch(instruction.second.source().source()) { case Source::DirectAddress: - operation += to_hex(instruction.offset(), 2, true); + operation += to_hex(uint8_t(instruction.second.offset())); break; default: - operation += to_string(instruction.source(), instruction, offset_length, immediate_length, InstructionSet::x86::DataSize::Word); + operation += to_string(instruction.second.source(), instruction.second, offset_length, immediate_length, InstructionSet::x86::DataSize::Word); break; } break; case Operation::OUT: - switch(instruction.destination().source()) { + switch(instruction.second.destination().source()) { case Source::DirectAddress: - operation += to_hex(instruction.offset(), 2, true); + operation += to_hex(uint8_t(instruction.second.offset())); break; default: - operation += to_string(instruction.destination(), instruction, offset_length, immediate_length, InstructionSet::x86::DataSize::Word); + operation += to_string(instruction.second.destination(), instruction.second, offset_length, immediate_length, InstructionSet::x86::DataSize::Word); break; } operation += ", "; - operation += to_string(instruction.source(), instruction, offset_length, immediate_length); + operation += to_string(instruction.second.source(), instruction.second, offset_length, immediate_length); break; // Rolls and shifts list eCX as a source on the understanding that everyone knows that rolls and shifts @@ -530,18 +568,18 @@ std::string InstructionSet::x86::to_string( case Operation::SAL: case Operation::SAR: case Operation::SHR: case Operation::SETMO: case Operation::SETMOC: - operation += to_string(instruction.destination(), instruction, offset_length, immediate_length); - switch(instruction.source().source()) { + operation += to_string(instruction.second.destination(), instruction.second, offset_length, immediate_length); + switch(instruction.second.source().source()) { case Source::None: break; case Source::eCX: operation += ", cl"; break; case Source::Immediate: // Providing an immediate operand of 1 is a little future-proofing by the decoder; the '1' // is actually implicit on a real 8088. So omit it. - if(instruction.operand() == 1) break; + if(instruction.second.operand() == 1) break; [[fallthrough]]; default: operation += ", "; - operation += to_string(instruction.source(), instruction, offset_length, immediate_length); + operation += to_string(instruction.second.source(), instruction.second, offset_length, immediate_length); break; } break; @@ -560,7 +598,7 @@ std::string InstructionSet::x86::to_string( //); template std::string InstructionSet::x86::to_string( - Instruction instruction, + std::pair> instruction, Model model, int offset_length, int immediate_length diff --git a/InstructionSets/x86/Instruction.hpp b/InstructionSets/x86/Instruction.hpp index d5b78d9b9..7a95dd540 100644 --- a/InstructionSets/x86/Instruction.hpp +++ b/InstructionSets/x86/Instruction.hpp @@ -69,11 +69,11 @@ enum class Operation: uint8_t { SBB, /// Subtract; source, destination, operand and displacement will be populated appropriately. SUB, - /// Unsigned multiply; multiplies the source value by AX or AL, storing the result in DX:AX or AX. + /// Unsigned multiply; multiplies the source value by EAX, AX or AL, storing the result in EDX:EAX, DX:AX or AX. MUL, - /// Single operand signed multiply; multiplies the source value by AX or AL, storing the result in DX:AX or AX. + /// Single operand signed multiply; multiplies the source value by EAX, AX or AL, storing the result in EDX:EAX, DX:AX or AX. IMUL_1, - /// Unsigned divide; divide the source value by AX or AL, storing the quotient in AL and the remainder in AH. + /// Unsigned divide; divide the AX, DX:AX or EDX:AX by the source(), storing the quotient in AL, AX or EAX and the remainder in AH, DX or EDX. DIV, /// Signed divide; divide the source value by AX or AL, storing the quotient in AL and the remainder in AH. IDIV, @@ -144,9 +144,9 @@ enum class Operation: uint8_t { /// Loads the destination with the source. MOV, - /// Negatives; source and destination point to the same thing, to negative. + /// Negatives; source indicates what to negative. NEG, - /// Logical NOT; source and destination point to the same thing, to negative. + /// Logical NOT; source indicates what to negative. NOT, /// Logical AND; source, destination, operand and displacement will be populated appropriately. AND, @@ -195,7 +195,7 @@ enum class Operation: uint8_t { CLI, /// Set carry flag. STC, - /// Set decimal flag. + /// Set direction flag. STD, /// Set interrupt flag. STI, @@ -370,6 +370,10 @@ enum class DataSize: uint8_t { None = 3, }; +template struct DataSizeType { using type = uint8_t; }; +template <> struct DataSizeType { using type = uint16_t; }; +template <> struct DataSizeType { using type = uint32_t; }; + constexpr int byte_size(DataSize size) { return (1 << int(size)) & 7; } @@ -383,6 +387,9 @@ enum class AddressSize: uint8_t { b32 = 1, }; +template struct AddressSizeType { using type = uint16_t; }; +template <> struct AddressSizeType { using type = uint32_t; }; + constexpr DataSize data_size(AddressSize size) { return DataSize(int(size) + 1); } @@ -458,16 +465,14 @@ enum class Repetition: uint8_t { }; /// @returns @c true if @c operation supports repetition mode @c repetition; @c false otherwise. -constexpr bool supports(Operation operation, Repetition repetition) { +constexpr bool supports(Operation operation, [[maybe_unused]] Repetition repetition) { switch(operation) { default: return false; - case Operation::INS: - case Operation::OUTS: - return repetition == Repetition::RepE; - case Operation::Invalid: // Retain context here; it's used as an intermediate // state sometimes. + case Operation::INS: + case Operation::OUTS: case Operation::CMPS: case Operation::LODS: case Operation::MOVS: @@ -475,8 +480,12 @@ constexpr bool supports(Operation operation, Repetition repetition) { case Operation::STOS: return true; - case Operation::IDIV: - return repetition == Repetition::RepNE; + // TODO: my new understanding is that the 8086 and 8088 recognise rep and repne on + // IDIV — and possibly DIV — as a quirk, affecting the outcome (possibly negativing the result?). + // So the test below should be a function of model, if I come to a conclusion about whether I'm + // going for fidelity to the instruction set as generally implemented, or to Intel's specific implementation. +// case Operation::IDIV: +// return repetition == Repetition::RepNE; } } @@ -589,10 +598,7 @@ class DataPointer { ); } - template constexpr Source source() const { - if constexpr (obscure_indirectNoBase) { - return (source_ >= Source::IndirectNoBase) ? Source::Indirect : source_; - } + constexpr Source source() const { return source_; } @@ -621,10 +627,14 @@ class DataPointer { } } - template constexpr Source base() const { - if constexpr (obscure_indirectNoBase) { - return (source_ <= Source::IndirectNoBase) ? Source::None : sib_.base(); - } + constexpr Source segment(Source segment_override) const { + // TODO: remove conditionality here. + if(segment_override != Source::None) return segment_override; + if(const auto segment = default_segment(); segment != Source::None) return segment; + return Source::DS; + } + + constexpr Source base() const { return sib_.base(); } @@ -689,6 +699,16 @@ template class Instruction { // [b4, b0]: dest. uint16_t source_data_dest_sib_ = 1 << 10; // So that ::Invalid doesn't seem to have a length extension. + // Note to future self: if source length continues to prove avoidable, reuse its four bits as: + // three bits: segment (as overridden, otherwise whichever operand has a segment, if either); + // one bit: an extra bit for Operation. + // + // Then what was the length extension will hold only a repetition, if any, and the lock bit. As luck would have + // it there are six valid segment registers so there is an available sentinel value to put into the segment + // field to indicate that there's an extension if necessary. A further three bits would need to be trimmed + // to do away with that extension entirely, but since lock is rarely used and repetitions apply only to a + // small number of operations I think it'd at least be a limited problem. + bool has_length_extension() const { return !((source_data_dest_sib_ >> 10) & 15); } @@ -762,7 +782,7 @@ template class Instruction { /// On x86 a segment override cannot modify the segment used as a destination in string instructions, /// or that used by stack instructions, but this function does not spend the time necessary to provide /// the correct default for those. - Source data_segment() const { + Source segment_override() const { if(!has_length_extension()) return Source::None; return Source( int(Source::ES) + @@ -774,15 +794,19 @@ template class Instruction { if(!has_length_extension()) return Repetition::None; return Repetition((length_extension() >> 4) & 3); } + + /// @returns The data size of this operation — e.g. `MOV AX, BX` has a data size of `::Word` but `MOV EAX, EBX` has a data size of + /// `::DWord`. This value is guaranteed never to be `DataSize::None` even for operations such as `CLI` that don't have operands and operate + /// on data that is not a byte, word or double word. DataSize operation_size() const { return DataSize(source_data_dest_sib_ >> 14); } - int length() const { - const int short_length = (source_data_dest_sib_ >> 10) & 15; - if(short_length) return short_length; - return length_extension() >> 6; - } +// int length() const { +// const int short_length = (source_data_dest_sib_ >> 10) & 15; +// if(short_length) return short_length; +// return length_extension() >> 6; +// } ImmediateT operand() const { const ImmediateT ops[] = {0, operand_extension()}; @@ -902,7 +926,7 @@ std::string to_string( /// If @c immediate_length is '2' or '4', truncates any printed immediate value to 2 or 4 digits if it is compatible with being that length. template std::string to_string( - Instruction instruction, + std::pair> instruction, Model model, int offset_length = 0, int immediate_length = 0); diff --git a/InstructionSets/x86/Interrupts.hpp b/InstructionSets/x86/Interrupts.hpp new file mode 100644 index 000000000..51366f11d --- /dev/null +++ b/InstructionSets/x86/Interrupts.hpp @@ -0,0 +1,24 @@ +// +// Interrupts.hpp +// Clock Signal +// +// Created by Thomas Harte on 06/10/2023. +// Copyright © 2023 Thomas Harte. All rights reserved. +// + +#ifndef InstructionSets_x86_Interrupts_h +#define InstructionSets_x86_Interrupts_h + +namespace InstructionSet::x86 { + +enum Interrupt { + DivideError = 0, + SingleStep = 1, + NMI = 2, + OneByte = 3, + OnOverflow = 4, +}; + +} + +#endif /* InstructionSets_x86_Interrupts_h */ diff --git a/InstructionSets/x86/Model.hpp b/InstructionSets/x86/Model.hpp index 4a4ac0bdb..e497cd5a6 100644 --- a/InstructionSets/x86/Model.hpp +++ b/InstructionSets/x86/Model.hpp @@ -9,6 +9,8 @@ #ifndef Model_h #define Model_h +#include + namespace InstructionSet::x86 { enum class Model { @@ -20,6 +22,9 @@ enum class Model { static constexpr bool is_32bit(Model model) { return model >= Model::i80386; } +template struct AddressT { using type = uint16_t; }; +template <> struct AddressT { using type = uint32_t; }; + } #endif /* Model_h */ diff --git a/InstructionSets/x86/Perform.hpp b/InstructionSets/x86/Perform.hpp new file mode 100644 index 000000000..02aeecde3 --- /dev/null +++ b/InstructionSets/x86/Perform.hpp @@ -0,0 +1,42 @@ +// +// Perform.hpp +// Clock Signal +// +// Created by Thomas Harte on 05/10/2023. +// Copyright © 2023 Thomas Harte. All rights reserved. +// + +#ifndef Perform_h +#define Perform_h + +#include "Instruction.hpp" +#include "Model.hpp" +#include "Status.hpp" + +namespace InstructionSet::x86 { + +/// Performs @c instruction querying @c registers and/or @c memory as required, using @c io for port input/output, +/// and providing any flow control effects to @c flow_controller. +/// +/// Any change in processor status will be applied to @c status. +template < + Model model, + typename InstructionT, + typename FlowControllerT, + typename RegistersT, + typename MemoryT, + typename IOT +> void perform( + const InstructionT &instruction, + Status &status, + FlowControllerT &flow_controller, + RegistersT ®isters, + MemoryT &memory, + IOT &io +); + +} + +#include "Implementation/PerformImplementation.hpp" + +#endif /* Perform_h */ diff --git a/InstructionSets/x86/Status.hpp b/InstructionSets/x86/Status.hpp new file mode 100644 index 000000000..9e6f77ffc --- /dev/null +++ b/InstructionSets/x86/Status.hpp @@ -0,0 +1,233 @@ +// +// Status.hpp +// Clock Signal +// +// Created by Thomas Harte on 05/10/2023. +// Copyright © 2023 Thomas Harte. All rights reserved. +// + +#ifndef InstructionSets_x86_Status_hpp +#define InstructionSets_x86_Status_hpp + +#include "../../Numeric/Carry.hpp" + +namespace InstructionSet::x86 { + +namespace ConditionCode { + +// +// Standard flags. +// + +static constexpr uint32_t Carry = 1 << 0; +static constexpr uint32_t Parity = 1 << 2; +static constexpr uint32_t AuxiliaryCarry = 1 << 4; +static constexpr uint32_t Zero = 1 << 6; +static constexpr uint32_t Sign = 1 << 7; +static constexpr uint32_t Trap = 1 << 8; +static constexpr uint32_t Interrupt = 1 << 9; +static constexpr uint32_t Direction = 1 << 10; +static constexpr uint32_t Overflow = 1 << 11; + +// +// 80286+ additions. +// + +static constexpr uint32_t IOPrivilege = (1 << 12) | (1 << 13); +static constexpr uint32_t NestedTask = 1 << 14; + +// +// 16-bit protected mode flags. +// + +static constexpr uint32_t ProtectionEnable = 1 << 16; +static constexpr uint32_t MonitorProcessorExtension = 1 << 17; +static constexpr uint32_t ProcessorExtensionExtension = 1 << 18; +static constexpr uint32_t TaskSwitch = 1 << 19; + +// +// 32-bit protected mode flags. +// + +static constexpr uint32_t Resume = 1 << 16; +static constexpr uint32_t VirtualMode = 1 << 17; + +} + +enum class Flag { + Carry, + AuxiliaryCarry, + Sign, + Overflow, + Trap, + Interrupt, + Direction, + Zero, + ParityOdd +}; + +enum class Condition { + Overflow, + Below, + Zero, + BelowOrEqual, + Sign, + ParityOdd, + Less, + LessOrEqual +}; + +class Status { + public: + using FlagT = uint32_t; + + // Flag getters. + template bool flag() const { + switch(flag) { + case Flag::Carry: return carry_; + case Flag::AuxiliaryCarry: return auxiliary_carry_; + case Flag::Sign: return sign_; + case Flag::Overflow: return overflow_; + case Flag::Trap: return trap_; + case Flag::Interrupt: return interrupt_; + case Flag::Direction: return direction_ < 0; + case Flag::Zero: return !zero_; + case Flag::ParityOdd: return not_parity_bit(); + } + } + + // Condition evaluation. + template bool condition() const { + switch(test) { + case Condition::Overflow: return flag(); + case Condition::Below: return flag(); + case Condition::Zero: return flag(); + case Condition::BelowOrEqual: return flag() || flag(); + case Condition::Sign: return flag(); + case Condition::ParityOdd: return flag(); + case Condition::Less: return flag() != flag(); + case Condition::LessOrEqual: return flag() || flag() != flag(); + } + } + + // Convenience setters. + + /// Sets all of @c flags as a function of @c value: + /// • Flag::Zero: sets the zero flag if @c value is zero; + /// • Flag::Sign: sets the sign flag if the top bit of @c value is one; + /// • Flag::ParityOdd: sets parity based on the low 8 bits of @c value; + /// • Flag::Carry: sets carry if @c value is non-zero; + /// • Flag::AuxiliaryCarry: sets auxiliary carry if @c value is non-zero; + /// • Flag::Overflow: sets overflow if @c value is non-zero; + /// • Flag::Interrupt: sets interrupt if @c value is non-zero; + /// • Flag::Trap: sets interrupt if @c value is non-zero; + /// • Flag::Direction: sets direction if @c value is non-zero. + template void set_from(IntT value) { + for(const auto flag: {flags...}) { + switch(flag) { + default: break; + case Flag::Zero: zero_ = value; break; + case Flag::Sign: sign_ = value & Numeric::top_bit(); break; + case Flag::ParityOdd: parity_ = value; break; + case Flag::Carry: carry_ = value; break; + case Flag::AuxiliaryCarry: auxiliary_carry_ = value; break; + case Flag::Overflow: overflow_ = value; break; + case Flag::Interrupt: interrupt_ = value; break; + case Flag::Trap: trap_ = value; break; + case Flag::Direction: direction_ = value ? -1 : 1; break; + } + } + } + template void set_from(FlagT value) { + set_from(value); + } + + template IntT carry_bit() const { return carry_ ? 1 : 0; } + bool not_parity_bit() const { + // x86 parity always considers the lowest 8-bits only. + auto result = static_cast(parity_); + result ^= result >> 4; + result ^= result >> 2; + result ^= result >> 1; + return result & 1; + } + + template IntT direction() const { return static_cast(direction_); } + + // Complete value get and set. + void set(uint16_t value) { + set_from(value & ConditionCode::Carry); + set_from(value & ConditionCode::AuxiliaryCarry); + set_from(value & ConditionCode::Overflow); + set_from(value & ConditionCode::Trap); + set_from(value & ConditionCode::Interrupt); + set_from(value & ConditionCode::Direction); + + set_from(value); + + set_from((~value) & ConditionCode::Zero); + set_from((~value) & ConditionCode::Parity); + } + + uint16_t get() const { + return + 0xf002 | + + (flag() ? ConditionCode::Carry : 0) | + (flag() ? ConditionCode::AuxiliaryCarry : 0) | + (flag() ? ConditionCode::Sign : 0) | + (flag() ? ConditionCode::Overflow : 0) | + (flag() ? ConditionCode::Trap : 0) | + (flag() ? ConditionCode::Interrupt : 0) | + (flag() ? ConditionCode::Direction : 0) | + (flag() ? ConditionCode::Zero : 0) | + + (flag() ? 0 : ConditionCode::Parity); + } + + std::string to_string() const { + std::string result; + + if(flag()) result += "O"; else result += "-"; + if(flag()) result += "D"; else result += "-"; + if(flag()) result += "I"; else result += "-"; + if(flag()) result += "T"; else result += "-"; + if(flag()) result += "S"; else result += "-"; + if(flag()) result += "Z"; else result += "-"; + result += "-"; + if(flag()) result += "A"; else result += "-"; + result += "-"; + if(!flag()) result += "P"; else result += "-"; + result += "-"; + if(flag()) result += "C"; else result += "-"; + + return result; + } + + bool operator ==(const Status &rhs) const { + return get() == rhs.get(); + } + + private: + // Non-zero => set; zero => unset. + uint32_t carry_; + uint32_t auxiliary_carry_; + uint32_t sign_; + uint32_t overflow_; + uint32_t trap_; + uint32_t interrupt_; + + // +1 = direction flag not set; + // -1 = direction flag set. + int32_t direction_; + + // Zero => set; non-zero => unset. + uint32_t zero_; + + // Odd number of bits => set; even => unset. + uint32_t parity_; +}; + +} + +#endif /* InstructionSets_x86_Status_hpp */ diff --git a/Numeric/Carry.hpp b/Numeric/Carry.hpp index fd17ed357..0d4a235fa 100644 --- a/Numeric/Carry.hpp +++ b/Numeric/Carry.hpp @@ -9,21 +9,73 @@ #ifndef Carry_hpp #define Carry_hpp +#include + namespace Numeric { -/// @returns @c true if there was carry out of @c bit when @c source1 and @c source2 were added, producing @c result. -template bool carried_out(IntT source1, IntT source2, IntT result) { +/// @returns @c true if from @c bit there was: +/// • carry after calculating @c lhs + @c rhs if @c is_add is true; or +/// • borrow after calculating @c lhs - @c rhs if @c is_add is false; +/// producing @c result. +template bool carried_out(IntT lhs, IntT rhs, IntT result) { + // Additive: + // // 0 and 0 => didn't. // 0 and 1 or 1 and 0 => did if 0. // 1 and 1 => did. - return IntT(1 << bit) & (source1 | source2) & ((source1 & source2) | ~result); + // + // Subtractive: + // + // 1 and 0 => didn't + // 1 and 1 or 0 and 0 => did if 1. + // 0 and 1 => did. + if constexpr (!is_add) { + rhs = ~rhs; + } + const bool carry = IntT(1 << bit) & (lhs | rhs) & ((lhs & rhs) | ~result); + if constexpr (!is_add) { + return !carry; + } else { + return carry; + } } -/// @returns @c true if there was carry into @c bit when @c source1 and @c source2 were added, producing @c result. -template bool carried_in(IntT source1, IntT source2, IntT result) { - // 0 and 0 or 1 and 1 => did if 1 - // 0 and 1 or 1 and 0 => did if 0 - return IntT(1 << bit) & (source1 ^ source2 ^ result); +/// @returns @c true if there was carry into @c bit when computing either: +/// • @c lhs + @c rhs; or +/// • @c lhs - @c rhs; +/// producing @c result. +template bool carried_in(IntT lhs, IntT rhs, IntT result) { + // 0 and 0 or 1 and 1 => did if 1. + // 0 and 1 or 1 and 0 => did if 0. + return IntT(1 << bit) & (lhs ^ rhs ^ result); +} + +/// @returns An int of type @c IntT with only the most-significant bit set. +template constexpr IntT top_bit() { + static_assert(!std::numeric_limits::is_signed); + constexpr IntT max = std::numeric_limits::max(); + return max - (max >> 1); +} + +/// @returns The number of bits in @c IntT. +template constexpr int bit_size() { + return sizeof(IntT) * 8; +} + +/// @returns An int with the top bit indicating whether overflow occurred during the calculation of +/// • @c lhs + @c rhs (if @c is_add is true); or +/// • @c lhs - @c rhs (if @c is_add is false) +/// and the result was @c result. All other bits will be clear. +template +IntT overflow(IntT lhs, IntT rhs, IntT result) { + const IntT output_changed = result ^ lhs; + const IntT input_differed = lhs ^ rhs; + + if constexpr (is_add) { + return top_bit() & output_changed & ~input_differed; + } else { + return top_bit() & output_changed & input_differed; + } } } diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index 81c6ba4b8..4cbaa5fbd 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -1048,7 +1048,6 @@ 4BE21219253FCE9C00435408 /* AppleIIgs.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BE21214253FCE9C00435408 /* AppleIIgs.cpp */; }; 4BE2121A253FCE9C00435408 /* AppleIIgs.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BE21214253FCE9C00435408 /* AppleIIgs.cpp */; }; 4BE34438238389E10058E78F /* AtariSTVideoTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BE34437238389E10058E78F /* AtariSTVideoTests.mm */; }; - 4BE3C69727CC32DC000EAD28 /* x86DataPointerTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BE3C69627CC32DC000EAD28 /* x86DataPointerTests.mm */; }; 4BE76CF922641ED400ACD6FA /* QLTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BE76CF822641ED300ACD6FA /* QLTests.mm */; }; 4BE8EB6625C750B50040BC40 /* DAT.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BE8EB6425C750B50040BC40 /* DAT.cpp */; }; 4BE90FFD22D5864800FB464D /* MacintoshVideoTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BE90FFC22D5864800FB464D /* MacintoshVideoTests.mm */; }; @@ -1126,6 +1125,10 @@ /* Begin PBXFileReference section */ 423BDC492AB24699008E37B6 /* 8088Tests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = 8088Tests.mm; sourceTree = ""; }; + 42437B342ACF02A9006DFED1 /* Status.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Status.hpp; sourceTree = ""; }; + 42437B352ACF0AA2006DFED1 /* Perform.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Perform.hpp; sourceTree = ""; }; + 42437B382ACF2798006DFED1 /* PerformImplementation.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = PerformImplementation.hpp; sourceTree = ""; }; + 42437B392AD07465006DFED1 /* Interrupts.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Interrupts.hpp; sourceTree = ""; }; 4281572E2AA0334300E16AA1 /* Carry.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Carry.hpp; sourceTree = ""; }; 428168372A16C25C008ECD27 /* LineLayout.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = LineLayout.hpp; sourceTree = ""; }; 428168392A37AFB4008ECD27 /* DispatcherTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = DispatcherTests.mm; sourceTree = ""; }; @@ -2210,9 +2213,7 @@ 4BE3231520532AA7006EF799 /* Target.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Target.hpp; sourceTree = ""; }; 4BE3231620532BED006EF799 /* Target.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Target.hpp; sourceTree = ""; }; 4BE34437238389E10058E78F /* AtariSTVideoTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = AtariSTVideoTests.mm; sourceTree = ""; }; - 4BE3C69327C793EF000EAD28 /* DataPointerResolver.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = DataPointerResolver.hpp; sourceTree = ""; }; 4BE3C69527CBC540000EAD28 /* Model.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Model.hpp; sourceTree = ""; }; - 4BE3C69627CC32DC000EAD28 /* x86DataPointerTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = x86DataPointerTests.mm; sourceTree = ""; }; 4BE76CF822641ED300ACD6FA /* QLTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = QLTests.mm; sourceTree = ""; }; 4BE845201F2FF7F100A5EA22 /* CRTC6845.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CRTC6845.hpp; sourceTree = ""; }; 4BE8EB5425C0E9D40040BC40 /* Disassembler.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Disassembler.hpp; sourceTree = ""; }; @@ -2323,6 +2324,14 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 42437B372ACF2798006DFED1 /* Implementation */ = { + isa = PBXGroup; + children = ( + 42437B382ACF2798006DFED1 /* PerformImplementation.hpp */, + ); + path = Implementation; + sourceTree = ""; + }; 42A5E8322ABBE16F00A0DD5D /* Neskell Tests */ = { isa = PBXGroup; children = ( @@ -4381,7 +4390,6 @@ 4B8DD3672633B2D400B3C866 /* SpectrumVideoContentionTests.mm */, 4B2AF8681E513FC20027EE29 /* TIATests.mm */, 4B1D08051E0F7A1100763741 /* TimeTests.mm */, - 4BE3C69627CC32DC000EAD28 /* x86DataPointerTests.mm */, 4BEE4BD325A26E2B00011BD2 /* x86DecoderTests.mm */, 4BDA8234261E8E000021AA19 /* Z80ContentionTests.mm */, 4BB73EB81B587A5100552FC2 /* Info.plist */, @@ -4989,10 +4997,13 @@ children = ( 4BEDA3B925B25563000C2DBD /* Decoder.cpp */, 4B69DEB52AB79E4F0055B217 /* Instruction.cpp */, - 4BE3C69327C793EF000EAD28 /* DataPointerResolver.hpp */, 4BEDA3B825B25563000C2DBD /* Decoder.hpp */, 4BEDA3DB25B2588F000C2DBD /* Instruction.hpp */, 4BE3C69527CBC540000EAD28 /* Model.hpp */, + 42437B352ACF0AA2006DFED1 /* Perform.hpp */, + 42437B342ACF02A9006DFED1 /* Status.hpp */, + 42437B372ACF2798006DFED1 /* Implementation */, + 42437B392AD07465006DFED1 /* Interrupts.hpp */, ); path = x86; sourceTree = ""; @@ -6235,7 +6246,6 @@ 4B7752AA28217E370073E2C5 /* ROMCatalogue.cpp in Sources */, 4B778F0323A5EBB00000D260 /* FAT12.cpp in Sources */, 4B778F4023A5F1910000D260 /* z8530.cpp in Sources */, - 4BE3C69727CC32DC000EAD28 /* x86DataPointerTests.mm in Sources */, 4B778EFD23A5EB8E0000D260 /* AppleDSK.cpp in Sources */, 4B7752B728217EF40073E2C5 /* Chipset.cpp in Sources */, 4B778EFB23A5EB7E0000D260 /* HFE.cpp in Sources */, diff --git a/OSBindings/Mac/Clock SignalTests/8088Tests.mm b/OSBindings/Mac/Clock SignalTests/8088Tests.mm index a7f81b22f..e13fd750e 100644 --- a/OSBindings/Mac/Clock SignalTests/8088Tests.mm +++ b/OSBindings/Mac/Clock SignalTests/8088Tests.mm @@ -18,6 +18,8 @@ #include "NSData+dataWithContentsOfGZippedFile.h" #include "../../../InstructionSets/x86/Decoder.hpp" +#include "../../../InstructionSets/x86/Perform.hpp" +#include "../../../Numeric/RegisterSizes.hpp" namespace { @@ -25,16 +27,302 @@ namespace { // provide their real path here. constexpr char TestSuiteHome[] = "/Users/tharte/Projects/ProcessorTests/8088/v1"; +using Status = InstructionSet::x86::Status; +struct Registers { + CPU::RegisterPair16 ax_; + uint8_t &al() { return ax_.halves.low; } + uint8_t &ah() { return ax_.halves.high; } + uint16_t &ax() { return ax_.full; } + + CPU::RegisterPair16 &axp() { return ax_; } + + CPU::RegisterPair16 cx_; + uint8_t &cl() { return cx_.halves.low; } + uint8_t &ch() { return cx_.halves.high; } + uint16_t &cx() { return cx_.full; } + + CPU::RegisterPair16 dx_; + uint8_t &dl() { return dx_.halves.low; } + uint8_t &dh() { return dx_.halves.high; } + uint16_t &dx() { return dx_.full; } + + CPU::RegisterPair16 bx_; + uint8_t &bl() { return bx_.halves.low; } + uint8_t &bh() { return bx_.halves.high; } + uint16_t &bx() { return bx_.full; } + + uint16_t sp_; + uint16_t &sp() { return sp_; } + + uint16_t bp_; + uint16_t &bp() { return bp_; } + + uint16_t si_; + uint16_t &si() { return si_; } + + uint16_t di_; + uint16_t &di() { return di_; } + + uint16_t es_, cs_, ds_, ss_; + + uint16_t ip_; + uint16_t &ip() { return ip_; } + + uint16_t &es() { return es_; } + uint16_t &cs() { return cs_; } + uint16_t &ds() { return ds_; } + uint16_t &ss() { return ss_; } + + bool operator ==(const Registers &rhs) const { + return + ax_.full == rhs.ax_.full && + cx_.full == rhs.cx_.full && + dx_.full == rhs.dx_.full && + bx_.full == rhs.bx_.full && + sp_ == rhs.sp_ && + bp_ == rhs.bp_ && + si_ == rhs.si_ && + di_ == rhs.di_ && + es_ == rhs.es_ && + cs_ == rhs.cs_ && + ds_ == rhs.ds_ && + si_ == rhs.si_ && + ip_ == rhs.ip_; + } +}; +struct Memory { + enum class Tag { + Seeded, + AccessExpected, + Accessed, + FlagsL, + FlagsH + }; + + std::unordered_map tags; + std::vector memory; + const Registers ®isters_; + + Memory(Registers ®isters) : registers_(registers) { + memory.resize(1024*1024); + } + + void clear() { + tags.clear(); + } + + void seed(uint32_t address, uint8_t value) { + memory[address] = value; + tags[address] = Tag::Seeded; + } + + void touch(uint32_t address) { + tags[address] = Tag::AccessExpected; + } + + uint32_t segment_base(InstructionSet::x86::Source segment) { + uint32_t physical_address; + using Source = InstructionSet::x86::Source; + switch(segment) { + default: physical_address = registers_.ds_; break; + case Source::ES: physical_address = registers_.es_; break; + case Source::CS: physical_address = registers_.cs_; break; + case Source::SS: physical_address = registers_.ss_; break; + } + return physical_address << 4; + } + + // Entry point used by the flow controller so that it can mark up locations at which the flags were written, + // so that defined-flag-only masks can be applied while verifying RAM contents. + template IntT &access(InstructionSet::x86::Source segment, uint16_t address, Tag tag) { + const uint32_t physical_address = (segment_base(segment) + address) & 0xf'ffff; + return access(physical_address, tag); + } + + // An additional entry point for the flow controller; on the original 8086 interrupt vectors aren't relative + // to a selector, they're just at an absolute location. + template IntT &access(uint32_t address, Tag tag) { + // Check for address wraparound + if(address >= 0x10'0001 - sizeof(IntT)) { + if constexpr (std::is_same_v) { + address &= 0xf'ffff; + } else { + if(address == 0xf'ffff) { + // This is a 16-bit access comprising the final byte in memory and the first. + write_back_address_[0] = address; + write_back_address_[1] = 0; + write_back_value_ = memory[write_back_address_[0]] | (memory[write_back_address_[1]] << 8); + return write_back_value_; + } else { + address &= 0xf'ffff; + } + } + } + + if(tags.find(address) == tags.end()) { + printf("Access to unexpected RAM address"); + } + tags[address] = tag; + return *reinterpret_cast(&memory[address]); + } + + // Entry point for the 8086; simply notes that memory was accessed. + template IntT &access([[maybe_unused]] InstructionSet::x86::Source segment, uint32_t address) { + if constexpr (std::is_same_v) { + // If this is a 16-bit access that runs past the end of the segment, it'll wrap back + // to the start. So the 16-bit value will need to be a local cache. + if(address == 0xffff) { + write_back_address_[0] = (segment_base(segment) + address) & 0xf'ffff; + write_back_address_[1] = (write_back_address_[0] - 65535) & 0xf'ffff; + write_back_value_ = memory[write_back_address_[0]] | (memory[write_back_address_[1]] << 8); + return write_back_value_; + } + } + return access(segment, address, Tag::Accessed); + } + + template + void write_back() { + if constexpr (std::is_same_v) { + if(write_back_address_[0] != NoWriteBack) { + memory[write_back_address_[0]] = write_back_value_ & 0xff; + memory[write_back_address_[1]] = write_back_value_ >> 8; + write_back_address_[0] = 0; + } + } + } + + static constexpr uint32_t NoWriteBack = 0; // A low byte address of 0 can't require write-back. + uint32_t write_back_address_[2] = {NoWriteBack, NoWriteBack}; + uint16_t write_back_value_; +}; +struct IO { + template void out([[maybe_unused]] uint16_t port, [[maybe_unused]] IntT value) {} + template IntT in([[maybe_unused]] uint16_t port) { return IntT(~0); } +}; +class FlowController { + public: + FlowController(Memory &memory, Registers ®isters, Status &status) : + memory_(memory), registers_(registers), status_(status) {} + + void did_iret() {} + void did_near_ret() {} + void did_far_ret() {} + + void interrupt(int index) { + const uint16_t address = static_cast(index) << 2; + const uint16_t new_ip = memory_.access(address, Memory::Tag::Accessed); + const uint16_t new_cs = memory_.access(address + 2, Memory::Tag::Accessed); + + push(status_.get(), true); + + using Flag = InstructionSet::x86::Flag; + status_.set_from(0); + + // Push CS and IP. + push(registers_.cs_); + push(registers_.ip_); + + registers_.cs_ = new_cs; + registers_.ip_ = new_ip; + } + + void call(uint16_t address) { + push(registers_.ip_); + jump(address); + } + + void call(uint16_t segment, uint16_t offset) { + push(registers_.cs_); + push(registers_.ip_); + jump(segment, offset); + } + + void jump(uint16_t address) { + registers_.ip_ = address; + } + + void jump(uint16_t segment, uint16_t address) { + registers_.cs_ = segment; + registers_.ip_ = address; + } + + void halt() {} + void wait() {} + + void begin_instruction() { + should_repeat_ = false; + } + void repeat_last() { + should_repeat_ = true; + } + bool should_repeat() const { + return should_repeat_; + } + + private: + Memory &memory_; + Registers ®isters_; + Status &status_; + bool should_repeat_ = false; + + void push(uint16_t value, bool is_flags = false) { + // Perform the push in two steps because it's possible for SP to underflow, and so that FlagsL and + // FlagsH can be set separately. + --registers_.sp_; + memory_.access( + InstructionSet::x86::Source::SS, + registers_.sp_, + is_flags ? Memory::Tag::FlagsH : Memory::Tag::Accessed + ) = value >> 8; + --registers_.sp_; + memory_.access( + InstructionSet::x86::Source::SS, + registers_.sp_, + is_flags ? Memory::Tag::FlagsL : Memory::Tag::Accessed + ) = value & 0xff; + } +}; + +struct ExecutionSupport { + InstructionSet::x86::Status status; + Registers registers; + Memory memory; + FlowController flow_controller; + IO io; + + ExecutionSupport() : memory(registers), flow_controller(memory, registers, status) {} + + void clear() { + memory.clear(); + } +}; + +struct FailedExecution { + std::string test_name; + std::string reason; + InstructionSet::x86::Instruction instruction; +}; + } @interface i8088Tests : XCTestCase @end -@implementation i8088Tests +@implementation i8088Tests { + ExecutionSupport execution_support; + std::vector execution_failures; +} - (NSArray *)testFiles { NSString *path = [NSString stringWithUTF8String:TestSuiteHome]; NSSet *allowList = [NSSet setWithArray:@[ + // Current execution failures: +// @"27.json.gz", // DAA +// @"2F.json.gz", // DAS +// @"D4.json.gz", // AAM +// @"F6.7.json.gz", // IDIV +// @"F7.7.json.gz", // IDIV ]]; NSSet *ignoreList = nil; @@ -58,22 +346,35 @@ constexpr char TestSuiteHome[] = "/Users/tharte/Projects/ProcessorTests/8088/v1" return [fullPaths sortedArrayUsingSelector:@selector(compare:)]; } -- (NSString *)toString:(const InstructionSet::x86::Instruction &)instruction offsetLength:(int)offsetLength immediateLength:(int)immediateLength { +- (NSArray *)testsInFile:(NSString *)file { + NSData *data = [NSData dataWithContentsOfGZippedFile:file]; + return [NSJSONSerialization JSONObjectWithData:data options:0 error:nil]; +} + +- (NSDictionary *)metadata { + NSString *path = [[NSString stringWithUTF8String:TestSuiteHome] stringByAppendingPathComponent:@"8088.json"]; + return [NSJSONSerialization JSONObjectWithData:[NSData dataWithContentsOfGZippedFile:path] options:0 error:nil]; +} + +- (NSString *)toString:(const std::pair> &)instruction offsetLength:(int)offsetLength immediateLength:(int)immediateLength { const auto operation = to_string(instruction, InstructionSet::x86::Model::i8086, offsetLength, immediateLength); return [[NSString stringWithUTF8String:operation.c_str()] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; } -- (bool)applyDecodingTest:(NSDictionary *)test file:(NSString *)file assert:(BOOL)assert { - using Decoder = InstructionSet::x86::Decoder; - Decoder decoder; - - // Build a vector of the instruction bytes; this makes manual step debugging easier. - NSArray *encoding = test[@"bytes"]; +- (std::vector)bytes:(NSArray *)encoding { std::vector data; data.reserve(encoding.count); for(NSNumber *number in encoding) { data.push_back([number intValue]); } + return data; +} + +- (bool)applyDecodingTest:(NSDictionary *)test file:(NSString *)file assert:(BOOL)assert { + InstructionSet::x86::Decoder decoder; + + // Build a vector of the instruction bytes; this makes manual step debugging easier. + const auto data = [self bytes:test[@"bytes"]]; auto hex_instruction = [&]() -> NSString * { NSMutableString *hexInstruction = [[NSMutableString alloc] init]; for(uint8_t byte: data) { @@ -83,30 +384,31 @@ constexpr char TestSuiteHome[] = "/Users/tharte/Projects/ProcessorTests/8088/v1" }; const auto decoded = decoder.decode(data.data(), data.size()); + const bool sizeMatched = decoded.first == data.size(); if(assert) { XCTAssert( - decoded.first == [encoding count], + sizeMatched, "Wrong length of instruction decoded for %@ — decoded %d rather than %lu from %@; file %@", test[@"name"], decoded.first, - (unsigned long)[encoding count], + (unsigned long)data.size(), hex_instruction(), file ); } - if(decoded.first != [encoding count]) { + if(!sizeMatched) { return false; } // The decoder doesn't preserve the original offset length, which makes no functional difference but // does affect the way that offsets are printed in the test set. NSSet *decodings = [NSSet setWithObjects: - [self toString:decoded.second offsetLength:4 immediateLength:4], - [self toString:decoded.second offsetLength:2 immediateLength:4], - [self toString:decoded.second offsetLength:0 immediateLength:4], - [self toString:decoded.second offsetLength:4 immediateLength:2], - [self toString:decoded.second offsetLength:2 immediateLength:2], - [self toString:decoded.second offsetLength:0 immediateLength:2], + [self toString:decoded offsetLength:4 immediateLength:4], + [self toString:decoded offsetLength:2 immediateLength:4], + [self toString:decoded offsetLength:0 immediateLength:4], + [self toString:decoded offsetLength:4 immediateLength:2], + [self toString:decoded offsetLength:2 immediateLength:2], + [self toString:decoded offsetLength:0 immediateLength:2], nil]; auto compare_decoding = [&](NSString *name) -> bool { @@ -117,11 +419,7 @@ constexpr char TestSuiteHome[] = "/Users/tharte/Projects/ProcessorTests/8088/v1" // Attempt clerical reconciliation: // - // TEMPORARY HACK: the test set incorrectly states 'bp+si' whenever it means 'bp+di'. - // Though it also uses 'bp+si' correctly when it means 'bp+si'. Until fixed, take - // a pass on potential issues there. - // - // SEPARATELY: The test suite retains a distinction between SHL and SAL, which the decoder doesn't. So consider that + // The test suite retains a distinction between SHL and SAL, which the decoder doesn't. So consider that // a potential point of difference. // // Also, the decoder treats INT3 and INT 3 as the same thing. So allow for a meshing of those. @@ -129,14 +427,11 @@ constexpr char TestSuiteHome[] = "/Users/tharte/Projects/ProcessorTests/8088/v1" while(!isEqual && adjustment) { NSString *alteredName = [test[@"name"] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; - if(adjustment & 4) { - alteredName = [alteredName stringByReplacingOccurrencesOfString:@"bp+si" withString:@"bp+di"]; - } if(adjustment & 2) { alteredName = [alteredName stringByReplacingOccurrencesOfString:@"shl" withString:@"sal"]; } if(adjustment & 1) { - alteredName = [alteredName stringByReplacingOccurrencesOfString:@"int3" withString:@"int 03h"]; + alteredName = [alteredName stringByReplacingOccurrencesOfString:@"int3" withString:@"int 3h"]; } isEqual = compare_decoding(alteredName); @@ -146,43 +441,232 @@ constexpr char TestSuiteHome[] = "/Users/tharte/Projects/ProcessorTests/8088/v1" if(assert) { XCTAssert( isEqual, - "%@ doesn't match %@ or similar, was %@ within %@", + "%@ doesn't match %@ or similar, was %@", test[@"name"], [decodings anyObject], - hex_instruction(), - file + hex_instruction() ); } return isEqual; } -- (void)testDecoding { - NSMutableSet *failures = [[NSMutableSet alloc] init]; - NSArray *testFiles = [self testFiles]; +- (void)populate:(Registers &)registers status:(InstructionSet::x86::Status &)status value:(NSDictionary *)value { + registers.ax_.full = [value[@"ax"] intValue]; + registers.bx_.full = [value[@"bx"] intValue]; + registers.cx_.full = [value[@"cx"] intValue]; + registers.dx_.full = [value[@"dx"] intValue]; - for(NSString *file in testFiles) { - NSData *data = [NSData dataWithContentsOfGZippedFile:file]; - NSArray *testsInFile = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil]; - NSUInteger successes = 0; - for(NSDictionary *test in testsInFile) { + registers.bp_ = [value[@"bp"] intValue]; + registers.cs_ = [value[@"cs"] intValue]; + registers.di_ = [value[@"di"] intValue]; + registers.ds_ = [value[@"ds"] intValue]; + registers.es_ = [value[@"es"] intValue]; + registers.si_ = [value[@"si"] intValue]; + registers.sp_ = [value[@"sp"] intValue]; + registers.ss_ = [value[@"ss"] intValue]; + registers.ip_ = [value[@"ip"] intValue]; + + const uint16_t flags = [value[@"flags"] intValue]; + status.set(flags); + + // Apply a quick test of flag packing/unpacking. + constexpr auto defined_flags = static_cast( + InstructionSet::x86::ConditionCode::Carry | + InstructionSet::x86::ConditionCode::Parity | + InstructionSet::x86::ConditionCode::AuxiliaryCarry | + InstructionSet::x86::ConditionCode::Zero | + InstructionSet::x86::ConditionCode::Sign | + InstructionSet::x86::ConditionCode::Trap | + InstructionSet::x86::ConditionCode::Interrupt | + InstructionSet::x86::ConditionCode::Direction | + InstructionSet::x86::ConditionCode::Overflow + ); + XCTAssert((status.get() & defined_flags) == (flags & defined_flags), + "Set status of %04x was returned as %04x", + flags & defined_flags, + (status.get() & defined_flags) + ); +} + +- (void)applyExecutionTest:(NSDictionary *)test metadata:(NSDictionary *)metadata { + InstructionSet::x86::Decoder decoder; + const auto data = [self bytes:test[@"bytes"]]; + const auto decoded = decoder.decode(data.data(), data.size()); + + execution_support.clear(); + + const uint16_t flags_mask = metadata[@"flags-mask"] ? [metadata[@"flags-mask"] intValue] : 0xffff; + NSDictionary *const initial_state = test[@"initial"]; + NSDictionary *const final_state = test[@"final"]; + + // Apply initial state. + InstructionSet::x86::Status initial_status; + for(NSArray *ram in initial_state[@"ram"]) { + execution_support.memory.seed([ram[0] intValue], [ram[1] intValue]); + } + for(NSArray *ram in final_state[@"ram"]) { + execution_support.memory.touch([ram[0] intValue]); + } + Registers initial_registers; + [self populate:initial_registers status:initial_status value:initial_state[@"regs"]]; + execution_support.status = initial_status; + execution_support.registers = initial_registers; + + // Execute instruction. + // + // TODO: enquire of the actual mechanism of repetition; if it were stateful as below then + // would it survive interrupts? So is it just IP adjustment? + execution_support.registers.ip_ += decoded.first; + do { + execution_support.flow_controller.begin_instruction(); + InstructionSet::x86::perform( + decoded.second, + execution_support.status, + execution_support.flow_controller, + execution_support.registers, + execution_support.memory, + execution_support.io + ); + } while (execution_support.flow_controller.should_repeat()); + + // Compare final state. + Registers intended_registers; + InstructionSet::x86::Status intended_status; + + bool ramEqual = true; + for(NSArray *ram in final_state[@"ram"]) { + const uint32_t address = [ram[0] intValue]; + + uint8_t mask = 0xff; + if(const auto tag = execution_support.memory.tags.find(address); tag != execution_support.memory.tags.end()) { + switch(tag->second) { + default: break; + case Memory::Tag::FlagsH: mask = flags_mask >> 8; break; + case Memory::Tag::FlagsL: mask = flags_mask & 0xff; break; + } + } + + if((execution_support.memory.memory[address] & mask) != ([ram[1] intValue] & mask)) { + ramEqual = false; + } + } + + [self populate:intended_registers status:intended_status value:final_state[@"regs"]]; + const bool registersEqual = intended_registers == execution_support.registers; + const bool statusEqual = (intended_status.get() & flags_mask) == (execution_support.status.get() & flags_mask); + + if(!statusEqual || !registersEqual || !ramEqual) { + FailedExecution failure; + failure.instruction = decoded.second; + failure.test_name = std::string([test[@"name"] UTF8String]); + + NSMutableArray *reasons = [[NSMutableArray alloc] init]; + if(!statusEqual) { + Status difference; + difference.set((intended_status.get() ^ execution_support.status.get()) & flags_mask); + [reasons addObject: + [NSString stringWithFormat:@"status differs; errors in %s", + difference.to_string().c_str()]]; + } + if(!registersEqual) { + NSMutableArray *registers = [[NSMutableArray alloc] init]; +#define Reg(x) \ + if(intended_registers.x() != execution_support.registers.x()) \ + [registers addObject: \ + [NSString stringWithFormat: \ + @#x" is %04x rather than %04x", execution_support.registers.x(), intended_registers.x()]]; + + Reg(ax); + Reg(cx); + Reg(dx); + Reg(bx); + Reg(sp); + Reg(bp); + Reg(si); + Reg(di); + Reg(ip); + Reg(es); + Reg(cs); + Reg(ds); + Reg(ss); + +#undef Reg + [reasons addObject:[NSString stringWithFormat: + @"registers don't match: %@", [registers componentsJoinedByString:@", "] + ]]; + } + if(!ramEqual) { + [reasons addObject:@"RAM contents don't match"]; + } + + failure.reason = std::string([reasons componentsJoinedByString:@"; "].UTF8String); + execution_failures.push_back(std::move(failure)); + } +} + +- (void)printFailures:(NSArray *)failures { + NSLog( + @"%ld failures out of %ld tests: %@", + failures.count, + [self testFiles].count, + [failures sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)]); +} + +- (void)testDecoding { + NSMutableArray *failures = [[NSMutableArray alloc] init]; + for(NSString *file in [self testFiles]) @autoreleasepool { + for(NSDictionary *test in [self testsInFile:file]) { // A single failure per instruction is fine. if(![self applyDecodingTest:test file:file assert:YES]) { [failures addObject:file]; // Attempt a second decoding, to provide a debugger hook. [self applyDecodingTest:test file:file assert:NO]; - break; } - ++successes; - } - if(successes != [testsInFile count]) { - NSLog(@"Failed after %ld successes", successes); } } - NSLog(@"%ld failures out of %ld tests: %@", failures.count, testFiles.count, [[failures allObjects] sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)]); + [self printFailures:failures]; +} + +- (void)testExecution { + NSDictionary *metadata = [self metadata]; + NSMutableArray *failures = [[NSMutableArray alloc] init]; + + for(NSString *file in [self testFiles]) @autoreleasepool { + const auto failures_before = execution_failures.size(); + + // Determine the metadata key. + NSString *const name = [file lastPathComponent]; + NSRange first_dot = [name rangeOfString:@"."]; + NSString *metadata_key = [name substringToIndex:first_dot.location]; + + // Grab the metadata. If it wants a reg field, inspect a little further. + NSDictionary *test_metadata = metadata[metadata_key]; + if(test_metadata[@"reg"]) { + test_metadata = test_metadata[@"reg"][[NSString stringWithFormat:@"%c", [name characterAtIndex:first_dot.location+1]]]; + } + + int index = 0; + for(NSDictionary *test in [self testsInFile:file]) { + [self applyExecutionTest:test metadata:test_metadata]; + ++index; + } + + if (execution_failures.size() != failures_before) { + [failures addObject:file]; + } + } + + XCTAssertEqual(execution_failures.size(), 0); + + for(const auto &failure: execution_failures) { + NSLog(@"Failed %s — %s", failure.test_name.c_str(), failure.reason.c_str()); + } + + NSLog(@"Files with failures were: %@", failures); } @end diff --git a/OSBindings/Mac/Clock SignalTests/x86DataPointerTests.mm b/OSBindings/Mac/Clock SignalTests/x86DataPointerTests.mm deleted file mode 100644 index 8db794516..000000000 --- a/OSBindings/Mac/Clock SignalTests/x86DataPointerTests.mm +++ /dev/null @@ -1,103 +0,0 @@ -// -// x86DataPointerTests.m -// Clock Signal -// -// Created by Thomas Harte on 27/02/2022. -// Copyright 2022 Thomas Harte. All rights reserved. -// - -#import - -#include "../../../InstructionSets/x86/DataPointerResolver.hpp" -#include - -using namespace InstructionSet::x86; - -@interface x86DataPointerTests : XCTestCase -@end - -@implementation x86DataPointerTests - -- (void)test16bitSize1 { - const DataPointer indirectPointer( - Source::eAX, Source::eDI, 0 - ); - const DataPointer registerPointer( - Source::eBX - ); - - struct Registers { - uint16_t ax = 0x1234, di = 0x00ee; - uint8_t bl = 0xaa; - - template DataT read() { - assert(is_sized(r)); - switch(r) { - case Register::AX: return ax; - case Register::BL: return bl; - case Register::DI: return di; - default: return 0; - } - } - template void write(DataT value) { - assert(is_sized(r)); - switch(r) { - case Register::BL: bl = value; break; - default: assert(false); - } - } - } registers; - - struct Memory { - std::map data; - - template DataT read(Source, uint32_t address) { - if(address == 0x1234 + 0x00ee) return 0xff; - return 0; - } - template void write(Source, uint32_t address, DataT value) { - data[address] = value; - } - } memory; - - // TODO: construct this more formally; the code below just assumes size = 1, which is not a contractual guarantee. - const auto instruction = Instruction(); - - using Resolver = DataPointerResolver; - const uint8_t memoryValue = Resolver::read( - registers, - memory, - instruction, - indirectPointer - ); - registers.ax = 0x0100; - Resolver::write( - registers, - memory, - instruction, - indirectPointer, - 0xef - ); - - XCTAssertEqual(memoryValue, 0xff); - XCTAssertEqual(memory.data[0x01ee], 0xef); - - const uint8_t registerValue = Resolver::read( - registers, - memory, - instruction, - registerPointer - ); - Resolver::write( - registers, - memory, - instruction, - registerPointer, - 0x93 - ); - - XCTAssertEqual(registerValue, 0xaa); - XCTAssertEqual(registers.bl, 0x93); -} - -@end diff --git a/OSBindings/Mac/Clock SignalTests/x86DecoderTests.mm b/OSBindings/Mac/Clock SignalTests/x86DecoderTests.mm index 7544cf22a..73ef9698c 100644 --- a/OSBindings/Mac/Clock SignalTests/x86DecoderTests.mm +++ b/OSBindings/Mac/Clock SignalTests/x86DecoderTests.mm @@ -12,7 +12,6 @@ #include #include #include "../../../InstructionSets/x86/Decoder.hpp" -#include "../../../InstructionSets/x86/DataPointerResolver.hpp" using namespace InstructionSet::x86; @@ -411,7 +410,7 @@ decode(const std::initializer_list &stream, bool set_32_bit = false) { // add DWORD PTR [edi-0x42],0x9f683aa9 // lock jp 0xfffffff0 (from 0000000e) test(instructions[0], DataSize::DWord, Operation::INC, Source::eDX); - XCTAssertEqual(instructions[0].data_segment(), Source::CS); + XCTAssertEqual(instructions[0].segment_override(), Source::CS); test(instructions[1], DataSize::Byte, Operation::OR, Source::Immediate, Source::eAX, 0x9); test(instructions[2], DataSize::DWord, Operation::ADD, Source::Immediate, ScaleIndexBase(Source::eDI), 0x9f683aa9, -0x42); test(instructions[3], Operation::JP, 0, -30); @@ -422,7 +421,7 @@ decode(const std::initializer_list &stream, bool set_32_bit = false) { // stos BYTE PTR es:[edi],al // pusha test(instructions[4], DataSize::Byte, Operation::MOV, Source::Immediate, Source::AH, 0xc1); - XCTAssertEqual(instructions[4].data_segment(), Source::DS); + XCTAssertEqual(instructions[4].segment_override(), Source::DS); test(instructions[5], DataSize::Word, Operation::POP, Source::None, Source::DS); test(instructions[6], DataSize::Byte, Operation::STOS); test(instructions[7], Operation::PUSHA); @@ -465,7 +464,7 @@ decode(const std::initializer_list &stream, bool set_32_bit = false) { test(instructions[21], DataSize::Byte, Operation::XOR, Source::Immediate, Source::eAX, 0x45); test(instructions[22], DataSize::DWord, Operation::LDS, ScaleIndexBase(Source::eCX), Source::eDX); test(instructions[23], DataSize::Byte, Operation::MOV, Source::eAX, Source::DirectAddress, 0xe4dba6d3); - XCTAssertEqual(instructions[23].data_segment(), Source::DS); + XCTAssertEqual(instructions[23].segment_override(), Source::DS); // pop ds // movs DWORD PTR es:[edi],DWORD PTR ds:[esi] diff --git a/Processors/6502/Implementation/6502Implementation.hpp b/Processors/6502/Implementation/6502Implementation.hpp index bd8dbd502..b55b3596f 100644 --- a/Processors/6502/Implementation/6502Implementation.hpp +++ b/Processors/6502/Implementation/6502Implementation.hpp @@ -327,7 +327,7 @@ template void Proces // All flags are set based only on the decimal result. flags_.zero_result = result; - flags_.carry = Numeric::carried_out<7>(a_, operand_, result); + flags_.carry = Numeric::carried_out(a_, operand_, result); flags_.negative_result = result; flags_.overflow = (( (result ^ a_) & (result ^ operand_) ) & 0x80) >> 1; @@ -377,7 +377,7 @@ template void Proces if(flags_.decimal && has_decimal_mode(personality)) { uint8_t result = a_ + operand_ + flags_.carry; flags_.zero_result = result; - flags_.carry = Numeric::carried_out<7>(a_, operand_, result); + flags_.carry = Numeric::carried_out(a_, operand_, result); // General ADC logic: //