diff --git a/InstructionSets/x86/Decoder.cpp b/InstructionSets/x86/Decoder.cpp index 442bfc37d..bc7bdb341 100644 --- a/InstructionSets/x86/Decoder.cpp +++ b/InstructionSets/x86/Decoder.cpp @@ -138,7 +138,7 @@ std::pair::InstructionT> Decoder::decode(con PartialBlock(0x00, ADD); break; case 0x06: Complete(PUSH, ES, None, data_size_); break; - case 0x07: Complete(POP, ES, None, data_size_); break; + case 0x07: Complete(POP, None, ES, data_size_); break; PartialBlock(0x08, OR); break; case 0x0e: Complete(PUSH, CS, None, data_size_); break; @@ -147,7 +147,7 @@ std::pair::InstructionT> Decoder::decode(con // prefixed with $0f. case 0x0f: if constexpr (model < Model::i80286) { - Complete(POP, CS, None, data_size_); + Complete(POP, None, CS, data_size_); } else { phase_ = Phase::InstructionPageF; } @@ -155,11 +155,11 @@ std::pair::InstructionT> Decoder::decode(con PartialBlock(0x10, ADC); break; case 0x16: Complete(PUSH, SS, None, DataSize::Word); break; - case 0x17: Complete(POP, SS, None, DataSize::Word); break; + case 0x17: Complete(POP, None, SS, DataSize::Word); break; PartialBlock(0x18, SBB); break; case 0x1e: Complete(PUSH, DS, None, DataSize::Word); break; - case 0x1f: Complete(POP, DS, None, DataSize::Word); break; + case 0x1f: Complete(POP, None, DS, DataSize::Word); break; PartialBlock(0x20, AND); break; case 0x26: segment_override_ = Source::ES; break; @@ -523,145 +523,147 @@ std::pair::InstructionT> Decoder::decode(con // MARK: - Additional F page of instructions. - if(phase_ == Phase::InstructionPageF && source != end) { - // Update the instruction acquired. - const uint8_t instr = *source; - ++source; - ++consumed_; + if constexpr (model >= Model::i80286) { + if(phase_ == Phase::InstructionPageF && source != end) { + // Update the instruction acquired. + const uint8_t instr = *source; + ++source; + ++consumed_; - // NB: to reach here, the instruction set must be at least - // that of an 80286. - switch(instr) { - default: undefined(); + // NB: to reach here, the instruction set must be at least + // that of an 80286. + switch(instr) { + default: undefined(); - case 0x00: MemRegReg(Invalid, MemRegSLDT_to_VERW, data_size_); break; - case 0x01: MemRegReg(Invalid, MemRegSGDT_to_LMSW, data_size_); break; - case 0x02: MemRegReg(LAR, Reg_MemReg, data_size_); break; - case 0x03: MemRegReg(LSL, Reg_MemReg, data_size_); break; - case 0x05: - Requires(i80286); - Complete(LOADALL, None, None, DataSize::Byte); - break; - case 0x06: Complete(CLTS, None, None, DataSize::Byte); break; + case 0x00: MemRegReg(Invalid, MemRegSLDT_to_VERW, data_size_); break; + case 0x01: MemRegReg(Invalid, MemRegSGDT_to_LMSW, data_size_); break; + case 0x02: MemRegReg(LAR, Reg_MemReg, data_size_); break; + case 0x03: MemRegReg(LSL, Reg_MemReg, data_size_); break; + case 0x05: + Requires(i80286); + Complete(LOADALL, None, None, DataSize::Byte); + break; + case 0x06: Complete(CLTS, None, None, DataSize::Byte); break; - case 0x20: - RequiresMin(i80386); - MemRegReg(MOVfromCr, Reg_MemReg, DataSize::DWord); - break; - case 0x21: - RequiresMin(i80386); - MemRegReg(MOVfromDr, Reg_MemReg, DataSize::DWord); - break; - case 0x22: - RequiresMin(i80386); - MemRegReg(MOVtoCr, Reg_MemReg, DataSize::DWord); - break; - case 0x23: - RequiresMin(i80386); - MemRegReg(MOVtoDr, Reg_MemReg, DataSize::DWord); - break; - case 0x24: - RequiresMin(i80386); - MemRegReg(MOVfromTr, Reg_MemReg, DataSize::DWord); - break; - case 0x26: - RequiresMin(i80386); - MemRegReg(MOVtoTr, Reg_MemReg, DataSize::DWord); - break; + case 0x20: + RequiresMin(i80386); + MemRegReg(MOVfromCr, Reg_MemReg, DataSize::DWord); + break; + case 0x21: + RequiresMin(i80386); + MemRegReg(MOVfromDr, Reg_MemReg, DataSize::DWord); + break; + case 0x22: + RequiresMin(i80386); + MemRegReg(MOVtoCr, Reg_MemReg, DataSize::DWord); + break; + case 0x23: + RequiresMin(i80386); + MemRegReg(MOVtoDr, Reg_MemReg, DataSize::DWord); + break; + case 0x24: + RequiresMin(i80386); + MemRegReg(MOVfromTr, Reg_MemReg, DataSize::DWord); + break; + case 0x26: + RequiresMin(i80386); + MemRegReg(MOVtoTr, Reg_MemReg, DataSize::DWord); + break; - case 0x70: RequiresMin(i80386); Displacement(JO, data_size_); break; - case 0x71: RequiresMin(i80386); Displacement(JNO, data_size_); break; - case 0x72: RequiresMin(i80386); Displacement(JB, data_size_); break; - case 0x73: RequiresMin(i80386); Displacement(JNB, data_size_); break; - case 0x74: RequiresMin(i80386); Displacement(JZ, data_size_); break; - case 0x75: RequiresMin(i80386); Displacement(JNZ, data_size_); break; - case 0x76: RequiresMin(i80386); Displacement(JBE, data_size_); break; - case 0x77: RequiresMin(i80386); Displacement(JNBE, data_size_); break; - case 0x78: RequiresMin(i80386); Displacement(JS, data_size_); break; - case 0x79: RequiresMin(i80386); Displacement(JNS, data_size_); break; - case 0x7a: RequiresMin(i80386); Displacement(JP, data_size_); break; - case 0x7b: RequiresMin(i80386); Displacement(JNP, data_size_); break; - case 0x7c: RequiresMin(i80386); Displacement(JL, data_size_); break; - case 0x7d: RequiresMin(i80386); Displacement(JNL, data_size_); break; - case 0x7e: RequiresMin(i80386); Displacement(JLE, data_size_); break; - case 0x7f: RequiresMin(i80386); Displacement(JNLE, data_size_); break; + case 0x70: RequiresMin(i80386); Displacement(JO, data_size_); break; + case 0x71: RequiresMin(i80386); Displacement(JNO, data_size_); break; + case 0x72: RequiresMin(i80386); Displacement(JB, data_size_); break; + case 0x73: RequiresMin(i80386); Displacement(JNB, data_size_); break; + case 0x74: RequiresMin(i80386); Displacement(JZ, data_size_); break; + case 0x75: RequiresMin(i80386); Displacement(JNZ, data_size_); break; + case 0x76: RequiresMin(i80386); Displacement(JBE, data_size_); break; + case 0x77: RequiresMin(i80386); Displacement(JNBE, data_size_); break; + case 0x78: RequiresMin(i80386); Displacement(JS, data_size_); break; + case 0x79: RequiresMin(i80386); Displacement(JNS, data_size_); break; + case 0x7a: RequiresMin(i80386); Displacement(JP, data_size_); break; + case 0x7b: RequiresMin(i80386); Displacement(JNP, data_size_); break; + case 0x7c: RequiresMin(i80386); Displacement(JL, data_size_); break; + case 0x7d: RequiresMin(i80386); Displacement(JNL, data_size_); break; + case 0x7e: RequiresMin(i80386); Displacement(JLE, data_size_); break; + case 0x7f: RequiresMin(i80386); Displacement(JNLE, data_size_); break; #define Set(x) \ RequiresMin(i80386); \ MemRegReg(SET##x, MemRegSingleOperand, DataSize::Byte); - case 0x90: Set(O); break; - case 0x91: Set(NO); break; - case 0x92: Set(B); break; - case 0x93: Set(NB); break; - case 0x94: Set(Z); break; - case 0x95: Set(NZ); break; - case 0x96: Set(BE); break; - case 0x97: Set(NBE); break; - case 0x98: Set(S); break; - case 0x99: Set(NS); break; - case 0x9a: Set(P); break; - case 0x9b: Set(NP); break; - case 0x9c: Set(L); break; - case 0x9d: Set(NL); break; - case 0x9e: Set(LE); break; - case 0x9f: Set(NLE); break; + case 0x90: Set(O); break; + case 0x91: Set(NO); break; + case 0x92: Set(B); break; + case 0x93: Set(NB); break; + case 0x94: Set(Z); break; + case 0x95: Set(NZ); break; + case 0x96: Set(BE); break; + case 0x97: Set(NBE); break; + case 0x98: Set(S); break; + case 0x99: Set(NS); break; + case 0x9a: Set(P); break; + case 0x9b: Set(NP); break; + case 0x9c: Set(L); break; + case 0x9d: Set(NL); break; + case 0x9e: Set(LE); break; + case 0x9f: Set(NLE); break; #undef Set - case 0xa0: RequiresMin(i80386); Complete(PUSH, FS, None, data_size_); break; - case 0xa1: RequiresMin(i80386); Complete(POP, FS, None, data_size_); break; - case 0xa3: RequiresMin(i80386); MemRegReg(BT, MemReg_Reg, data_size_); break; - case 0xa4: - RequiresMin(i80386); - MemRegReg(SHLDimm, Reg_MemReg, data_size_); - operand_size_ = DataSize::Byte; - break; - case 0xa5: - RequiresMin(i80386); - MemRegReg(SHLDCL, MemReg_Reg, data_size_); - break; - case 0xa8: RequiresMin(i80386); Complete(PUSH, GS, None, data_size_); break; - case 0xa9: RequiresMin(i80386); Complete(POP, GS, None, data_size_); break; - case 0xab: RequiresMin(i80386); MemRegReg(BTS, MemReg_Reg, data_size_); break; - case 0xac: - RequiresMin(i80386); - MemRegReg(SHRDimm, Reg_MemReg, data_size_); - operand_size_ = DataSize::Byte; - break; - case 0xad: - RequiresMin(i80386); - MemRegReg(SHRDCL, MemReg_Reg, data_size_); - break; - case 0xaf: - RequiresMin(i80386); - MemRegReg(IMUL_2, Reg_MemReg, data_size_); - break; + case 0xa0: RequiresMin(i80386); Complete(PUSH, FS, None, data_size_); break; + case 0xa1: RequiresMin(i80386); Complete(POP, None, FS, data_size_); break; + case 0xa3: RequiresMin(i80386); MemRegReg(BT, MemReg_Reg, data_size_); break; + case 0xa4: + RequiresMin(i80386); + MemRegReg(SHLDimm, Reg_MemReg, data_size_); + operand_size_ = DataSize::Byte; + break; + case 0xa5: + RequiresMin(i80386); + MemRegReg(SHLDCL, MemReg_Reg, data_size_); + break; + case 0xa8: RequiresMin(i80386); Complete(PUSH, GS, None, data_size_); break; + case 0xa9: RequiresMin(i80386); Complete(POP, None, GS, data_size_); break; + case 0xab: RequiresMin(i80386); MemRegReg(BTS, MemReg_Reg, data_size_); break; + case 0xac: + RequiresMin(i80386); + MemRegReg(SHRDimm, Reg_MemReg, data_size_); + operand_size_ = DataSize::Byte; + break; + case 0xad: + RequiresMin(i80386); + MemRegReg(SHRDCL, MemReg_Reg, data_size_); + break; + case 0xaf: + RequiresMin(i80386); + MemRegReg(IMUL_2, Reg_MemReg, data_size_); + break; - case 0xb2: RequiresMin(i80386); MemRegReg(LSS, Reg_MemReg, data_size_); break; - case 0xb3: RequiresMin(i80386); MemRegReg(BTR, MemReg_Reg, data_size_); break; - case 0xb4: RequiresMin(i80386); MemRegReg(LFS, Reg_MemReg, data_size_); break; - case 0xb5: RequiresMin(i80386); MemRegReg(LGS, Reg_MemReg, data_size_); break; - case 0xb6: - RequiresMin(i80386); - MemRegReg(MOVZX, Reg_MemReg, DataSize::Byte); - break; - case 0xb7: - RequiresMin(i80386); - MemRegReg(MOVZX, Reg_MemReg, DataSize::Word); - break; - case 0xba: RequiresMin(i80386); MemRegReg(Invalid, MemRegBT_to_BTC, data_size_); break; - case 0xbb: RequiresMin(i80386); MemRegReg(BTC, MemReg_Reg, data_size_); break; - case 0xbc: RequiresMin(i80386); MemRegReg(BSF, MemReg_Reg, data_size_); break; - case 0xbd: RequiresMin(i80386); MemRegReg(BSR, MemReg_Reg, data_size_); break; - case 0xbe: - RequiresMin(i80386); - MemRegReg(MOVSX, Reg_MemReg, DataSize::Byte); - break; - case 0xbf: - RequiresMin(i80386); - MemRegReg(MOVSX, Reg_MemReg, DataSize::Word); - break; + case 0xb2: RequiresMin(i80386); MemRegReg(LSS, Reg_MemReg, data_size_); break; + case 0xb3: RequiresMin(i80386); MemRegReg(BTR, MemReg_Reg, data_size_); break; + case 0xb4: RequiresMin(i80386); MemRegReg(LFS, Reg_MemReg, data_size_); break; + case 0xb5: RequiresMin(i80386); MemRegReg(LGS, Reg_MemReg, data_size_); break; + case 0xb6: + RequiresMin(i80386); + MemRegReg(MOVZX, Reg_MemReg, DataSize::Byte); + break; + case 0xb7: + RequiresMin(i80386); + MemRegReg(MOVZX, Reg_MemReg, DataSize::Word); + break; + case 0xba: RequiresMin(i80386); MemRegReg(Invalid, MemRegBT_to_BTC, data_size_); break; + case 0xbb: RequiresMin(i80386); MemRegReg(BTC, MemReg_Reg, data_size_); break; + case 0xbc: RequiresMin(i80386); MemRegReg(BSF, MemReg_Reg, data_size_); break; + case 0xbd: RequiresMin(i80386); MemRegReg(BSR, MemReg_Reg, data_size_); break; + case 0xbe: + RequiresMin(i80386); + MemRegReg(MOVSX, Reg_MemReg, DataSize::Byte); + break; + case 0xbf: + RequiresMin(i80386); + MemRegReg(MOVSX, Reg_MemReg, DataSize::Word); + break; + } } } @@ -979,18 +981,20 @@ std::pair::InstructionT> Decoder::decode(con // MARK: - ScaleIndexBase - if(phase_ == Phase::ScaleIndexBase && source != end) { - sib_ = *source; - ++source; - ++consumed_; + if constexpr (is_32bit(model)) { + if(phase_ == Phase::ScaleIndexBase && source != end) { + sib_ = *source; + ++source; + ++consumed_; - // Potentially record the lack of a base. - if(displacement_size_ == DataSize::None && (uint8_t(sib_)&7) == 5) { - source_ = (source_ == Source::Indirect) ? Source::IndirectNoBase : source_; - destination_ = (destination_ == Source::Indirect) ? Source::IndirectNoBase : destination_; + // Potentially record the lack of a base. + if(displacement_size_ == DataSize::None && (uint8_t(sib_)&7) == 5) { + source_ = (source_ == Source::Indirect) ? Source::IndirectNoBase : source_; + destination_ = (destination_ == Source::Indirect) ? Source::IndirectNoBase : destination_; + } + + phase_ = (displacement_size_ != DataSize::None || operand_size_ != DataSize::None) ? Phase::DisplacementOrOperand : Phase::ReadyToPost; } - - phase_ = (displacement_size_ != DataSize::None || operand_size_ != DataSize::None) ? Phase::DisplacementOrOperand : Phase::ReadyToPost; } // MARK: - Displacement and operand. @@ -1041,6 +1045,18 @@ std::pair::InstructionT> Decoder::decode(con // MARK: - Check for completion. if(phase_ == Phase::ReadyToPost) { + // TODO: map to #UD where applicable; build LOCK into the Operation type, buying an extra bit for the operation? + // + // As of the P6 Intel stipulates that: + // + // "The LOCK prefix can be prepended only to the following instructions and to those forms of the instructions + // that use a memory operand: ADD, ADC, AND, BTC, BTR, BTS, CMPXCHG, DEC, INC, NEG, NOT, OR, SBB, SUB, XOR, + // XADD, and XCHG." + // + // ... and the #UD exception will be raised if LOCK is encountered elsewhere. So adding 17 additional + // operations would unlock an extra bit of storage for a net gain of 239 extra operation types and thereby + // alleviating any concerns over whether there'll be space to handle MMX, floating point extensions, etc. + const auto result = std::make_pair( consumed_, InstructionT( diff --git a/InstructionSets/x86/Implementation/PerformImplementation.hpp b/InstructionSets/x86/Implementation/PerformImplementation.hpp index 5d840289e..3f84adc73 100644 --- a/InstructionSets/x86/Implementation/PerformImplementation.hpp +++ b/InstructionSets/x86/Implementation/PerformImplementation.hpp @@ -18,23 +18,21 @@ namespace InstructionSet::x86 { -template +template IntT *resolve( InstructionT &instruction, Source source, DataPointer pointer, - RegistersT ®isters, - MemoryT &memory, + ContextT &context, IntT *none = nullptr, IntT *immediate = nullptr ); -template +template uint32_t address( InstructionT &instruction, DataPointer pointer, - RegistersT ®isters, - MemoryT &memory + ContextT &context ) { // TODO: non-word indexes and bases. if constexpr (source == Source::DirectAddress) { @@ -43,8 +41,8 @@ uint32_t address( uint32_t address; uint16_t zero = 0; - address = *resolve(instruction, pointer.index(), pointer, registers, memory, &zero); - if constexpr (is_32bit(model)) { + address = *resolve(instruction, pointer.index(), pointer, context, &zero); + if constexpr (is_32bit(ContextT::model)) { address <<= pointer.scale(); } address += instruction.offset(); @@ -52,91 +50,93 @@ uint32_t address( if constexpr (source == Source::IndirectNoBase) { return address; } - return address + *resolve(instruction, pointer.base(), pointer, registers, memory); + return address + *resolve(instruction, pointer.base(), pointer, context); } -template -IntT *register_(RegistersT ®isters) { +template +IntT *register_(ContextT &context) { + static constexpr bool supports_dword = is_32bit(ContextT::model); + 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; } + if constexpr (supports_dword && std::is_same_v) { return &context.registers.eax(); } + else if constexpr (std::is_same_v) { return &context.registers.ax(); } + else if constexpr (std::is_same_v) { return &context.registers.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; } + if constexpr (supports_dword && std::is_same_v) { return &context.registers.ecx(); } + else if constexpr (std::is_same_v) { return &context.registers.cx(); } + else if constexpr (std::is_same_v) { return &context.registers.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; } + if constexpr (supports_dword && std::is_same_v) { return &context.registers.edx(); } + else if constexpr (std::is_same_v) { return &context.registers.dx(); } + else if constexpr (std::is_same_v) { return &context.registers.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; } + if constexpr (supports_dword && std::is_same_v) { return &context.registers.ebx(); } + else if constexpr (std::is_same_v) { return &context.registers.bx(); } + else if constexpr (std::is_same_v) { return &context.registers.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; } + if constexpr (supports_dword && std::is_same_v) { return &context.registers.esp(); } + else if constexpr (std::is_same_v) { return &context.registers.sp(); } + else if constexpr (std::is_same_v) { return &context.registers.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; } + if constexpr (supports_dword && std::is_same_v) { return &context.registers.ebp(); } + else if constexpr (std::is_same_v) { return &context.registers.bp(); } + else if constexpr (std::is_same_v) { return &context.registers.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; } + if constexpr (supports_dword && std::is_same_v) { return &context.registers.esi(); } + else if constexpr (std::is_same_v) { return &context.registers.si(); } + else if constexpr (std::is_same_v) { return &context.registers.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; } + if constexpr (supports_dword && std::is_same_v) { return &context.registers.edi(); } + else if constexpr (std::is_same_v) { return &context.registers.di(); } + else if constexpr (std::is_same_v) { return &context.registers.bh(); } + else { return nullptr; } default: return nullptr; } } -template +template uint32_t address( InstructionT &instruction, DataPointer pointer, - RegistersT ®isters, - MemoryT &memory + ContextT &context ) { + // TODO: at least on the 8086 this isn't how register 'addresses' are resolved; instead whatever was the last computed address + // remains in the address register and is returned. Find out what other x86s do and make a decision. 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); + case Source::eAX: return *register_(context); + case Source::eCX: return *register_(context); + case Source::eDX: return *register_(context); + case Source::eBX: return *register_(context); + case Source::eSPorAH: return *register_(context); + case Source::eBPorCH: return *register_(context); + case Source::eSIorDH: return *register_(context); + case Source::eDIorBH: return *register_(context); + case Source::Indirect: return address(instruction, pointer, context); + case Source::IndirectNoBase: return address(instruction, pointer, context); + case Source::DirectAddress: return address(instruction, pointer, context); } } -template +template IntT *resolve( InstructionT &instruction, Source source, DataPointer pointer, - RegistersT ®isters, - MemoryT &memory, + ContextT &context, IntT *none, IntT *immediate ) { @@ -146,24 +146,24 @@ IntT *resolve( // * 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); + case Source::eAX: return register_(context); + case Source::eCX: return register_(context); + case Source::eDX: return register_(context); + case Source::eBX: return register_(context); + case Source::eSPorAH: return register_(context); + case Source::eBPorCH: return register_(context); + case Source::eSIorDH: return register_(context); + case Source::eDIorBH: return register_(context); // 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; + case Source::ES: if constexpr (std::is_same_v) return &context.registers.es(); else return nullptr; + case Source::CS: if constexpr (std::is_same_v) return &context.registers.cs(); else return nullptr; + case Source::SS: if constexpr (std::is_same_v) return &context.registers.ss(); else return nullptr; + case Source::DS: if constexpr (std::is_same_v) return &context.registers.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::FS: if constexpr (is_32bit(ContextT::model) && std::is_same_v) return &context.registers.fs(); else return nullptr; + case Source::GS: if constexpr (is_32bit(ContextT::model) && std::is_same_v) return &context.registers.gs(); else return nullptr; case Source::Immediate: *immediate = instruction.operand(); @@ -172,40 +172,40 @@ IntT *resolve( case Source::None: return none; case Source::Indirect: - target_address = address(instruction, pointer, registers, memory); + target_address = address(instruction, pointer, context); break; case Source::IndirectNoBase: - target_address = address(instruction, pointer, registers, memory); + target_address = address(instruction, pointer, context); break; case Source::DirectAddress: - target_address = address(instruction, pointer, registers, memory); + target_address = address(instruction, pointer, context); break; } // If execution has reached here then a memory fetch is required. // Do it and exit. - return &memory.template access(instruction.data_segment(), target_address); + return &context.memory.template access(instruction.data_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(); +// 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, ContextT &context) { + context.registers.sp_ -= sizeof(IntT); + context.memory.template access( + Source::SS, + context.registers.sp_) = value; + context.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); +template +IntT pop(ContextT &context) { + const auto value = context.memory.template access( + Source::SS, + context.registers.sp_); + context.registers.sp_ += sizeof(IntT); return value; } @@ -217,7 +217,8 @@ IntT pop(MemoryT &memory, RegistersT ®isters) { // 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 +template +void aaa(CPU::RegisterPair16 &ax, ContextT &context) { // P. 313 /* IF ((AL AND 0FH) > 9) OR (AF = 1) THEN @@ -235,17 +236,18 @@ inline void aaa(CPU::RegisterPair16 &ax, Status &status) { // P. 313 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()) { + if((ax.halves.low & 0x0f) > 9 || context.status.template flag()) { ax.halves.low += 6; ++ax.halves.high; - status.set_from(1); + context.status.template set_from(1); } else { - status.set_from(0); + context.status.template set_from(0); } ax.halves.low &= 0x0f; } -inline void aad(CPU::RegisterPair16 &ax, uint8_t imm, Status &status) { +template +void aad(CPU::RegisterPair16 &ax, uint8_t imm, ContextT &context) { /* tempAL ← AL; tempAH ← AH; @@ -258,11 +260,11 @@ inline void aad(CPU::RegisterPair16 &ax, uint8_t imm, Status &status) { */ ax.halves.low = ax.halves.low + (ax.halves.high * imm); ax.halves.high = 0; - status.set_from(ax.halves.low); + context.status.template set_from(ax.halves.low); } -template -void aam(CPU::RegisterPair16 &ax, uint8_t imm, Status &status, FlowControllerT &flow_controller) { +template +void aam(CPU::RegisterPair16 &ax, uint8_t imm, ContextT &context) { /* tempAL ← AL; AH ← tempAL / imm8; (* imm8 is set to 0AH for the AAD mnemonic *) @@ -276,16 +278,17 @@ void aam(CPU::RegisterPair16 &ax, uint8_t imm, Status &status, FlowControllerT & If ... an immediate value of 0 is used, it will cause a #DE (divide error) exception. */ if(!imm) { - flow_controller.interrupt(Interrupt::DivideError); + interrupt(Interrupt::DivideError, context); return; } ax.halves.high = ax.halves.low / imm; ax.halves.low = ax.halves.low % imm; - status.set_from(ax.halves.low); + context.status.template set_from(ax.halves.low); } -inline void aas(CPU::RegisterPair16 &ax, Status &status) { +template +void aas(CPU::RegisterPair16 &ax, ContextT &context) { /* IF ((AL AND 0FH) > 9) OR (AF = 1) THEN @@ -303,17 +306,18 @@ inline void aas(CPU::RegisterPair16 &ax, Status &status) { 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()) { + if((ax.halves.low & 0x0f) > 9 || context.status.template flag()) { ax.halves.low -= 6; --ax.halves.high; - status.set_from(1); + context.status.template set_from(1); } else { - status.set_from(0); + context.status.template set_from(0); } ax.halves.low &= 0x0f; } -inline void daa(uint8_t &al, Status &status) { +template +void daa(uint8_t &al, ContextT &context) { /* (as modified by https://www.felixcloutier.com/x86/daa ...) @@ -343,28 +347,29 @@ inline void daa(uint8_t &al, Status &status) { 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); + const auto old_carry = context.status.template flag(); + context.status.template set_from(0); - if((al & 0x0f) > 0x09 || status.flag()) { - status.set_from(old_carry | (al > 0xf9)); + if((al & 0x0f) > 0x09 || context.status.template flag()) { + context.status.template set_from(old_carry | (al > 0xf9)); al += 0x06; - status.set_from(1); + context.status.template set_from(1); } else { - status.set_from(0); + context.status.template set_from(0); } if(old_al > 0x99 || old_carry) { al += 0x60; - status.set_from(1); + context.status.template set_from(1); } else { - status.set_from(0); + context.status.template set_from(0); } - status.set_from(al); + context.status.template set_from(al); } -inline void das(uint8_t &al, Status &status) { +template +void das(uint8_t &al, ContextT &context) { /* (as modified by https://www.felixcloutier.com/x86/daa ...) @@ -394,75 +399,75 @@ inline void das(uint8_t &al, Status &status) { 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); + const auto old_carry = context.status.template flag(); + context.status.template set_from(0); - if((al & 0x0f) > 0x09 || status.flag()) { - status.set_from(old_carry | (al < 0x06)); + if((al & 0x0f) > 0x09 || context.status.template flag()) { + context.status.template set_from(old_carry | (al < 0x06)); al -= 0x06; - status.set_from(1); + context.status.template set_from(1); } else { - status.set_from(0); + context.status.template set_from(0); } if(old_al > 0x99 || old_carry) { al -= 0x60; - status.set_from(1); + context.status.template set_from(1); } else { - status.set_from(0); + context.status.template set_from(0); } - status.set_from(al); + context.status.template set_from(al); } -template -void add(IntT &destination, IntT source, Status &status) { +template +void add(IntT &destination, IntT source, ContextT &context) { /* 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); + const IntT result = destination + source + (with_carry ? context.status.template carry_bit() : 0); - status.set_from( + context.status.template set_from( Numeric::carried_out() - 1>(destination, source, result)); - status.set_from( + context.status.template set_from( Numeric::carried_in<4>(destination, source, result)); - status.set_from( + context.status.template set_from( Numeric::overflow(destination, source, result)); - status.set_from(result); + context.status.template set_from(result); destination = result; } -template -void sub(IntT &destination, IntT source, Status &status) { +template +void sub(IntT &destination, IntT source, ContextT &context) { /* 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); + const IntT result = destination - source - (with_borrow ? context.status.template carry_bit() : 0); - status.set_from( + context.status.template set_from( Numeric::carried_out() - 1>(destination, source, result)); - status.set_from( + context.status.template set_from( Numeric::carried_in<4>(destination, source, result)); - status.set_from( + context.status.template set_from( Numeric::overflow(destination, source, result)); - status.set_from(result); + context.status.template set_from(result); if constexpr (write_back) { destination = result; } } -template -void test(IntT &destination, IntT source, Status &status) { +template +void test(IntT &destination, IntT source, ContextT &context) { /* TEMP ← SRC1 AND SRC2; SF ← MSB(TEMP); @@ -481,8 +486,8 @@ void test(IntT &destination, IntT source, Status &status) { */ const IntT result = destination & source; - status.set_from(0); - status.set_from(result); + context.status.template set_from(0); + context.status.template set_from(result); } template @@ -495,8 +500,8 @@ void xchg(IntT &destination, IntT &source) { std::swap(destination, source); } -template -void mul(IntT &destination_high, IntT &destination_low, IntT source, Status &status) { +template +void mul(IntT &destination_high, IntT &destination_low, IntT source, ContextT &context) { /* IF byte operation THEN @@ -514,11 +519,11 @@ void mul(IntT &destination_high, IntT &destination_low, IntT source, Status &sta */ destination_high = (destination_low * source) >> (8 * sizeof(IntT)); destination_low *= source; - status.set_from(destination_high); + context.status.template set_from(destination_high); } -template -void imul(IntT &destination_high, IntT &destination_low, IntT source, Status &status) { +template +void imul(IntT &destination_high, IntT &destination_low, IntT source, ContextT &context) { /* (as modified by https://www.felixcloutier.com/x86/daa ...) @@ -549,11 +554,11 @@ void imul(IntT &destination_high, IntT &destination_low, IntT source, Status &st 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); + context.status.template set_from(destination_high != sign_extension); } -template -void div(IntT &destination_high, IntT &destination_low, IntT source, FlowControllerT &flow_controller) { +template +void div(IntT &destination_high, IntT &destination_low, IntT source, ContextT &context) { /* IF SRC = 0 THEN #DE; (* divide error *) @@ -592,7 +597,7 @@ void div(IntT &destination_high, IntT &destination_low, IntT source, FlowControl The CF, OF, SF, ZF, AF, and PF flags are undefined. */ if(!source) { - flow_controller.interrupt(Interrupt::DivideError); + interrupt(Interrupt::DivideError, context); return; } @@ -600,7 +605,7 @@ void div(IntT &destination_high, IntT &destination_low, IntT source, FlowControl 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); + interrupt(Interrupt::DivideError, context); return; } @@ -608,8 +613,8 @@ void div(IntT &destination_high, IntT &destination_low, IntT source, FlowControl destination_high = dividend % source; } -template -void idiv(IntT &destination_high, IntT &destination_low, IntT source, FlowControllerT &flow_controller) { +template +void idiv(IntT &destination_high, IntT &destination_low, IntT source, ContextT &context) { /* IF SRC = 0 THEN #DE; (* divide error *) @@ -648,7 +653,7 @@ void idiv(IntT &destination_high, IntT &destination_low, IntT source, FlowContro The CF, OF, SF, ZF, AF, and PF flags are undefined. */ if(!source) { - flow_controller.interrupt(Interrupt::DivideError); + interrupt(Interrupt::DivideError, context); return; } @@ -657,7 +662,7 @@ void idiv(IntT &destination_high, IntT &destination_low, IntT source, FlowContro 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); + interrupt(Interrupt::DivideError, context); return; } @@ -665,8 +670,8 @@ void idiv(IntT &destination_high, IntT &destination_low, IntT source, FlowContro destination_high = dividend % sIntT(source); } -template -void inc(IntT &destination, Status &status) { +template +void inc(IntT &destination, ContextT &context) { /* DEST ← DEST + 1; */ @@ -676,13 +681,13 @@ void inc(IntT &destination, Status &status) { */ ++destination; - status.set_from(destination == Numeric::top_bit()); - status.set_from(((destination - 1) ^ destination) & 0x10); - status.set_from(destination); + context.status.template set_from(destination == Numeric::top_bit()); + context.status.template set_from(((destination - 1) ^ destination) & 0x10); + context.status.template set_from(destination); } -template -void jump(bool condition, IntT displacement, RegistersT ®isters, FlowControllerT &flow_controller) { +template +void jump(bool condition, IntT displacement, ContextT &context) { /* IF condition THEN @@ -696,36 +701,36 @@ void jump(bool condition, IntT displacement, RegistersT ®isters, FlowControll // TODO: proper behaviour in 32-bit. if(condition) { - flow_controller.jump(registers.ip() + displacement); + context.flow_controller.jump(context.registers.ip() + displacement); } } -template -void loop(IntT &counter, OffsetT displacement, RegistersT ®isters, FlowControllerT &flow_controller) { +template +void loop(IntT &counter, OffsetT displacement, ContextT &context) { --counter; if(counter) { - flow_controller.jump(registers.ip() + displacement); + context.flow_controller.jump(context.registers.ip() + displacement); } } -template -void loope(IntT &counter, OffsetT displacement, RegistersT ®isters, Status &status, FlowControllerT &flow_controller) { +template +void loope(IntT &counter, OffsetT displacement, ContextT &context) { --counter; - if(counter && status.flag()) { - flow_controller.jump(registers.ip() + displacement); + if(counter && context.status.template flag()) { + context.flow_controller.jump(context.registers.ip() + displacement); } } -template -void loopne(IntT &counter, OffsetT displacement, RegistersT ®isters, Status &status, FlowControllerT &flow_controller) { +template +void loopne(IntT &counter, OffsetT displacement, ContextT &context) { --counter; - if(counter && !status.flag()) { - flow_controller.jump(registers.ip() + displacement); + if(counter && !context.status.template flag()) { + context.flow_controller.jump(context.registers.ip() + displacement); } } -template -void dec(IntT &destination, Status &status) { +template +void dec(IntT &destination, ContextT &context) { /* DEST ← DEST - 1; */ @@ -733,16 +738,16 @@ void dec(IntT &destination, Status &status) { 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()); + context.status.template set_from(destination == Numeric::top_bit()); --destination; - status.set_from(destination); - status.set_from(((destination + 1) ^ destination) & 0x10); + context.status.template set_from(destination); + context.status.template set_from(((destination + 1) ^ destination) & 0x10); } -template -void and_(IntT &destination, IntT source, Status &status) { +template +void and_(IntT &destination, IntT source, ContextT &context) { /* DEST ← DEST AND SRC; */ @@ -752,12 +757,12 @@ void and_(IntT &destination, IntT source, Status &status) { */ destination &= source; - status.set_from(0); - status.set_from(destination); + context.status.template set_from(0); + context.status.template set_from(destination); } -template -void or_(IntT &destination, IntT source, Status &status) { +template +void or_(IntT &destination, IntT source, ContextT &context) { /* DEST ← DEST OR SRC; */ @@ -767,12 +772,12 @@ void or_(IntT &destination, IntT source, Status &status) { */ destination |= source; - status.set_from(0); - status.set_from(destination); + context.status.template set_from(0); + context.status.template set_from(destination); } -template -void xor_(IntT &destination, IntT source, Status &status) { +template +void xor_(IntT &destination, IntT source, ContextT &context) { /* DEST ← DEST XOR SRC; */ @@ -782,12 +787,12 @@ void xor_(IntT &destination, IntT source, Status &status) { */ destination ^= source; - status.set_from(0); - status.set_from(destination); + context.status.template set_from(0); + context.status.template set_from(destination); } -template -void neg(IntT &destination, Status &status) { +template +void neg(IntT &destination, ContextT &context) { /* IF DEST = 0 THEN CF ← 0 @@ -799,13 +804,13 @@ void neg(IntT &destination, Status &status) { 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))); + context.status.template 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); + context.status.template set_from(destination); + context.status.template set_from(destination == Numeric::top_bit()); + context.status.template set_from(destination); } template @@ -819,151 +824,157 @@ void not_(IntT &destination) { destination = ~destination; } -template -void call_relative(IntT offset, RegistersT ®isters, FlowControllerT &flow_controller) { - flow_controller.call(registers.ip() + offset); +template +void call_relative(IntT offset, ContextT &context) { + push(context.registers.ip(), context); + context.flow_controller.jump(context.registers.ip() + offset); } -template -void call_absolute(IntT target, FlowControllerT &flow_controller) { - flow_controller.call(target); +template +void call_absolute(IntT target, ContextT &context) { + push(context.registers.ip(), context); + context.flow_controller.jump(target); } -template -void jump_absolute(IntT target, FlowControllerT &flow_controller) { - flow_controller.jump(target); +template +void jump_absolute(IntT target, ContextT &context) { + context.flow_controller.jump(target); } -template -void call_far(InstructionT &instruction, - FlowControllerT &flow_controller, - RegistersT ®isters, - MemoryT &memory -) { +template +void call_far(InstructionT &instruction, ContextT &context) { + // TODO: eliminate 16-bit assumption below. + const Source source_segment = instruction.data_segment(); + context.memory.preauthorise_stack_write(sizeof(uint16_t) * 2); + + uint16_t source_address; + const auto pointer = instruction.destination(); + switch(pointer.source()) { + default: + case Source::Immediate: + push(context.registers.cs(), context); + push(context.registers.ip(), context); + context.flow_controller.jump(instruction.segment(), instruction.offset()); + return; + + case Source::Indirect: + source_address = address(instruction, pointer, context); + break; + case Source::IndirectNoBase: + source_address = address(instruction, pointer, context); + break; + case Source::DirectAddress: + source_address = address(instruction, pointer, context); + break; + } + + context.memory.preauthorise_read(source_segment, source_address, sizeof(uint16_t) * 2); + const uint16_t offset = context.memory.template access(source_segment, source_address); + source_address += 2; + const uint16_t segment = context.memory.template access(source_segment, source_address); + + // At least on an 8086, the stack writes occur after the target address read. + push(context.registers.cs(), context); + push(context.registers.ip(), context); + + context.flow_controller.jump(segment, offset); +} + +template +void jump_far(InstructionT &instruction, ContextT &context) { // 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::Immediate: context.flow_controller.jump(instruction.segment(), instruction.offset()); return; case Source::Indirect: - source_address = address(instruction, pointer, registers, memory); + source_address = address(instruction, pointer, context); break; case Source::IndirectNoBase: - source_address = address(instruction, pointer, registers, memory); + source_address = address(instruction, pointer, context); break; case Source::DirectAddress: - source_address = address(instruction, pointer, registers, memory); + source_address = address(instruction, pointer, context); break; } const Source source_segment = instruction.data_segment(); + context.memory.preauthorise_read(source_segment, source_address, sizeof(uint16_t) * 2); - const uint16_t offset = memory.template access(source_segment, source_address); + const uint16_t offset = context.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); + const uint16_t segment = context.memory.template access(source_segment, source_address); + context.flow_controller.jump(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 = instruction.data_segment(); - - 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) { +template +void iret(ContextT &context) { // 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(); + context.memory.preauthorise_stack_read(sizeof(uint16_t) * 3); + const auto ip = pop(context); + const auto cs = pop(context); + context.status.set(pop(context)); + context.flow_controller.jump(cs, ip); } -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_near(InstructionT instruction, ContextT &context) { + const auto ip = pop(context); + context.registers.sp() += instruction.operand(); + context.flow_controller.jump(ip); } -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 ret_far(InstructionT instruction, ContextT &context) { + context.memory.preauthorise_stack_read(sizeof(uint16_t) * 2); + const auto ip = pop(context); + const auto cs = pop(context); + context.registers.sp() += instruction.operand(); + context.flow_controller.jump(cs, ip); } -template +template void ld( InstructionT &instruction, uint16_t &destination, - MemoryT &memory, - RegistersT ®isters + ContextT &context ) { const auto pointer = instruction.source(); - auto source_address = address(instruction, pointer, registers, memory); + auto source_address = address(instruction, pointer, context); const Source source_segment = instruction.data_segment(); - destination = memory.template access(source_segment, source_address); + context.memory.preauthorise_read(source_segment, source_address, 4); + destination = context.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; + case Source::DS: context.registers.ds() = context.memory.template access(source_segment, source_address); break; + case Source::ES: context.registers.es() = context.memory.template access(source_segment, source_address); break; } } -template +template void lea( const InstructionT &instruction, IntT &destination, - MemoryT &memory, - RegistersT ®isters + ContextT &context ) { // TODO: address size. - destination = IntT(address(instruction, instruction.source(), registers, memory)); + destination = IntT(address(instruction, instruction.source(), context)); } -template +template void xlat( const InstructionT &instruction, - MemoryT &memory, - RegistersT ®isters + ContextT &context ) { AddressT address; if constexpr (std::is_same_v) { - address = registers.bx() + registers.al(); + address = context.registers.bx() + context.registers.al(); } - registers.al() = memory.template access(instruction.data_segment(), address); + context.registers.al() = context.memory.template access(instruction.data_segment(), address); } template @@ -971,40 +982,37 @@ 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); +template +void into(ContextT &context) { + if(context.status.template flag()) { + interrupt(Interrupt::OnOverflow, context); } } -inline void sahf(uint8_t &ah, Status &status) { +template +void sahf(uint8_t &ah, ContextT &context) { /* 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); + context.status.template set_from(ah); + context.status.template set_from(!(ah & 0x40)); + context.status.template set_from(ah & 0x10); + context.status.template set_from(!(ah & 0x04)); + context.status.template set_from(ah & 0x01); } -inline void lahf(uint8_t &ah, Status &status) { +template +void lahf(uint8_t &ah, ContextT &context) { /* 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) | + (context.status.template flag() ? 0x80 : 0x00) | + (context.status.template flag() ? 0x40 : 0x00) | + (context.status.template flag() ? 0x10 : 0x00) | + (context.status.template flag() ? 0x00 : 0x04) | 0x02 | - (status.flag() ? 0x01 : 0x00); + (context.status.template flag() ? 0x01 : 0x00); } template @@ -1025,32 +1033,37 @@ void cwd(IntT &dx, IntT ax) { } // 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 clc(ContextT &context) { context.status.template set_from(0); } +template +void cld(ContextT &context) { context.status.template set_from(0); } +template +void cli(ContextT &context) { context.status.template set_from(0); } +template +void stc(ContextT &context) { context.status.template set_from(1); } +template +void std(ContextT &context) { context.status.template set_from(1); } +template +void sti(ContextT &context) { context.status.template set_from(1); } +template +void cmc(ContextT &context) { + context.status.template set_from(!context.status.template flag()); } -template -void setmo(IntT &destination, Status &status) { +template +void salc(uint8_t &al, ContextT &context) { + al = context.status.template flag() ? 0xff : 0x00; +} + +template +void setmo(IntT &destination, ContextT &context) { destination = ~0; - status.set_from(0); - status.set_from(destination); + context.status.template set_from(0); + context.status.template 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) { +template +void rcl(IntT &destination, uint8_t count, ContextT &context) { /* (* RCL and RCR instructions *) SIZE ← OperandSize @@ -1081,7 +1094,7 @@ inline void rcl(IntT &destination, uint8_t count, Status &status) { 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(); + auto carry = context.status.template carry_bit(); switch(temp_count) { case 0: break; case Numeric::bit_size(): { @@ -1099,14 +1112,14 @@ inline void rcl(IntT &destination, uint8_t count, Status &status) { } break; } - status.set_from(carry); - status.set_from( + context.status.template set_from(carry); + context.status.template set_from( ((destination >> (Numeric::bit_size() - 1)) & 1) ^ carry ); } -template -inline void rcr(IntT &destination, uint8_t count, Status &status) { +template +void rcr(IntT &destination, uint8_t count, ContextT &context) { /* (* RCR instruction operation *) IF COUNT = 1 @@ -1121,8 +1134,8 @@ inline void rcr(IntT &destination, uint8_t count, Status &status) { tempCOUNT ← tempCOUNT – 1; OD; */ - auto carry = status.carry_bit(); - status.set_from( + auto carry = context.status.template carry_bit(); + context.status.template set_from( ((destination >> (Numeric::bit_size() - 1)) & 1) ^ carry ); @@ -1144,11 +1157,11 @@ inline void rcr(IntT &destination, uint8_t count, Status &status) { } break; } - status.set_from(carry); + context.status.template set_from(carry); } -template -inline void rol(IntT &destination, uint8_t count, Status &status) { +template +void rol(IntT &destination, uint8_t count, ContextT &context) { /* (* ROL and ROR instructions *) SIZE ← OperandSize @@ -1188,14 +1201,14 @@ inline void rol(IntT &destination, uint8_t count, Status &status) { (destination >> (Numeric::bit_size() - temp_count)); } - status.set_from(destination & 1); - status.set_from( + context.status.template set_from(destination & 1); + context.status.template set_from( ((destination >> (Numeric::bit_size() - 1)) ^ destination) & 1 ); } -template -inline void ror(IntT &destination, uint8_t count, Status &status) { +template +void ror(IntT &destination, uint8_t count, ContextT &context) { /* (* ROL and ROR instructions *) SIZE ← OperandSize @@ -1235,8 +1248,8 @@ inline void ror(IntT &destination, uint8_t count, Status &status) { (destination << (Numeric::bit_size() - temp_count)); } - status.set_from(destination & Numeric::top_bit()); - status.set_from( + context.status.template set_from(destination & Numeric::top_bit()); + context.status.template set_from( (destination ^ (destination << 1)) & Numeric::top_bit() ); } @@ -1297,35 +1310,35 @@ inline void ror(IntT &destination, uint8_t count, Status &status) { 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) { +template +void sal(IntT &destination, uint8_t count, ContextT &context) { switch(count) { case 0: return; case Numeric::bit_size(): - status.set_from(destination & 1); + context.status.template set_from(destination & 1); destination = 0; break; default: if(count > Numeric::bit_size()) { - status.set_from(0); + context.status.template set_from(0); destination = 0; } else { const auto mask = (Numeric::top_bit() >> (count - 1)); - status.set_from( + context.status.template set_from( destination & mask ); - status.set_from( + context.status.template set_from( (destination ^ (destination << 1)) & mask ); destination <<= count; } break; } - status.set_from(destination); + context.status.template set_from(destination); } -template -inline void sar(IntT &destination, uint8_t count, Status &status) { +template +void sar(IntT &destination, uint8_t count, ContextT &context) { if(!count) { return; } @@ -1333,46 +1346,46 @@ inline void sar(IntT &destination, uint8_t count, Status &status) { const IntT sign = Numeric::top_bit() & destination; if(count >= Numeric::bit_size()) { destination = sign ? IntT(~0) : IntT(0); - status.set_from(sign); + context.status.template set_from(sign); } else { const IntT mask = 1 << (count - 1); - status.set_from(destination & mask); + context.status.template set_from(destination & mask); destination = (destination >> count) | (sign ? ~(IntT(~0) >> count) : 0); } - status.set_from(0); - status.set_from(destination); + context.status.template set_from(0); + context.status.template set_from(destination); } -template -inline void shr(IntT &destination, uint8_t count, Status &status) { +template +void shr(IntT &destination, uint8_t count, ContextT &context) { if(!count) { return; } - status.set_from(Numeric::top_bit() & destination); + context.status.template set_from(Numeric::top_bit() & destination); if(count == Numeric::bit_size()) { - status.set_from(Numeric::top_bit() & destination); + context.status.template set_from(Numeric::top_bit() & destination); destination = 0; } else if(count > Numeric::bit_size()) { - status.set_from(0); + context.status.template set_from(0); destination = 0; } else { const IntT mask = 1 << (count - 1); - status.set_from(destination & mask); + context.status.template set_from(destination & mask); destination >>= count; } - status.set_from(destination); + context.status.template set_from(destination); } -template -void popf(MemoryT &memory, RegistersT ®isters, Status &status) { - status.set(pop(memory, registers)); +template +void popf(ContextT &context) { + context.status.set(pop(context)); } -template -void pushf(MemoryT &memory, RegistersT ®isters, Status &status) { - uint16_t value = status.get(); - push(value, memory, registers); +template +void pushf(ContextT &context) { + uint16_t value = context.status.get(); + push(value, context); } template @@ -1380,8 +1393,8 @@ bool repetition_over(const AddressT &eCX) { return repetition != Repetition::None && !eCX; } -template -void repeat([[maybe_unused]] Status &status, AddressT &eCX, FlowControllerT &flow_controller) { +template +void repeat(AddressT &eCX, ContextT &context) { if( repetition == Repetition::None || // No repetition => stop. !(--eCX) // [e]cx is zero after being decremented => stop. @@ -1390,156 +1403,182 @@ void repeat([[maybe_unused]] Status &status, AddressT &eCX, FlowControllerT &flo } if constexpr (repetition != Repetition::Rep) { // If this is RepE or RepNE, also test the zero flag. - if((repetition == Repetition::RepNE) == status.flag()) { + if((repetition == Repetition::RepNE) == context.status.template flag()) { return; } } - flow_controller.repeat_last(); + context.flow_controller.repeat_last(); } -template -void cmps(const InstructionT &instruction, AddressT &eCX, AddressT &eSI, AddressT &eDI, MemoryT &memory, Status &status, FlowControllerT &flow_controller) { +template +void cmps(const InstructionT &instruction, AddressT &eCX, AddressT &eSI, AddressT &eDI, ContextT &context) { if(repetition_over(eCX)) { return; } - IntT lhs = memory.template access(instruction.data_segment(), eSI); - const IntT rhs = memory.template access(Source::ES, eDI); - eSI += status.direction() * sizeof(IntT); - eDI += status.direction() * sizeof(IntT); + IntT lhs = context.memory.template access(instruction.data_segment(), eSI); + const IntT rhs = context.memory.template access(Source::ES, eDI); + eSI += context.status.template direction() * sizeof(IntT); + eDI += context.status.template direction() * sizeof(IntT); - Primitive::sub(lhs, rhs, status); + Primitive::sub(lhs, rhs, context); - repeat(status, eCX, flow_controller); + repeat(eCX, context); } -template -void scas(AddressT &eCX, AddressT &eDI, IntT &eAX, MemoryT &memory, Status &status, FlowControllerT &flow_controller) { +template +void scas(AddressT &eCX, AddressT &eDI, IntT &eAX, ContextT &context) { if(repetition_over(eCX)) { return; } - const IntT rhs = memory.template access(Source::ES, eDI); - eDI += status.direction() * sizeof(IntT); + const IntT rhs = context.memory.template access(Source::ES, eDI); + eDI += context.status.template direction() * sizeof(IntT); - Primitive::sub(eAX, rhs, status); + Primitive::sub(eAX, rhs, context); - repeat(status, eCX, flow_controller); + repeat(eCX, context); } -template -void lods(const InstructionT &instruction, AddressT &eCX, AddressT &eSI, IntT &eAX, MemoryT &memory, Status &status, FlowControllerT &flow_controller) { +template +void lods(const InstructionT &instruction, AddressT &eCX, AddressT &eSI, IntT &eAX, ContextT &context) { if(repetition_over(eCX)) { return; } - eAX = memory.template access(instruction.data_segment(), eSI); - eSI += status.direction() * sizeof(IntT); + eAX = context.memory.template access(instruction.data_segment(), eSI); + eSI += context.status.template direction() * sizeof(IntT); - repeat(status, eCX, flow_controller); + repeat(eCX, context); } -template -void movs(const InstructionT &instruction, AddressT &eCX, AddressT &eSI, AddressT &eDI, MemoryT &memory, Status &status, FlowControllerT &flow_controller) { +template +void movs(const InstructionT &instruction, AddressT &eCX, AddressT &eSI, AddressT &eDI, ContextT &context) { if(repetition_over(eCX)) { return; } - memory.template access(Source::ES, eDI) = memory.template access(instruction.data_segment(), eSI); + context.memory.template access(Source::ES, eDI) = + context.memory.template access(instruction.data_segment(), eSI); - eSI += status.direction() * sizeof(IntT); - eDI += status.direction() * sizeof(IntT); + eSI += context.status.template direction() * sizeof(IntT); + eDI += context.status.template direction() * sizeof(IntT); - repeat(status, eCX, flow_controller); + repeat(eCX, context); } -template -void stos(AddressT &eCX, AddressT &eDI, IntT &eAX, MemoryT &memory, Status &status, FlowControllerT &flow_controller) { +template +void stos(AddressT &eCX, AddressT &eDI, IntT &eAX, ContextT &context) { if(repetition_over(eCX)) { return; } - memory.template access(Source::ES, eDI) = eAX; - eDI += status.direction() * sizeof(IntT); + context.memory.template access(Source::ES, eDI) = eAX; + eDI += context.status.template direction() * sizeof(IntT); - repeat(status, eCX, flow_controller); + repeat(eCX, context); } -template -void outs(const InstructionT &instruction, AddressT &eCX, uint16_t port, AddressT &eSI, MemoryT &memory, IOT &io, Status &status, FlowControllerT &flow_controller) { +template +void outs(const InstructionT &instruction, AddressT &eCX, uint16_t port, AddressT &eSI, ContextT &context) { if(repetition_over(eCX)) { return; } - io.template out(port, memory.template access(instruction.data_segment(), eSI)); - eSI += status.direction() * sizeof(IntT); + context.io.template out( + port, + context.memory.template access(instruction.data_segment(), eSI) + ); + eSI += context.status.template direction() * sizeof(IntT); - repeat(status, eCX, flow_controller); + repeat(eCX, context); } -template -void ins(AddressT &eCX, uint16_t port, AddressT &eDI, MemoryT &memory, IOT &io, Status &status, FlowControllerT &flow_controller) { +template +void ins(AddressT &eCX, uint16_t port, AddressT &eDI, ContextT &context) { if(repetition_over(eCX)) { return; } - memory.template access(Source::ES, eDI) = io.template in(port); - eDI += status.direction() * sizeof(IntT); + context.memory.template access(Source::ES, eDI) = context.io.template in(port); + eDI += context.status.template direction() * sizeof(IntT); - repeat(status, eCX, flow_controller); + repeat(eCX, context); } -template -void out(uint16_t port, IntT value, IOT &io) { - io.template out(port, value); +template +void out(uint16_t port, IntT value, ContextT &context) { + context.io.template out(port, value); } -template -void in(uint16_t port, IntT &value, IOT &io) { - value = io.template in(port); +template +void in(uint16_t port, IntT &value, ContextT &context) { + value = context.io.template in(port); } } template < - Model model, DataSize data_size, AddressSize address_size, typename InstructionT, - typename FlowControllerT, - typename RegistersT, - typename MemoryT, - typename IOT + typename ContextT > void perform( const InstructionT &instruction, - Status &status, - FlowControllerT &flow_controller, - RegistersT ®isters, - MemoryT &memory, - IOT &io + ContextT &context ) { using IntT = typename DataSizeType::type; using AddressT = typename AddressSizeType::type; - // Establish source() and destination() shorthand to fetch data if necessary. + // Establish source() and destination() shorthands to fetch data if necessary. + // + // C++17, which this project targets at the time of writing, does not provide templatised lambdas. + // So the following division is in part a necessity. + // + // (though GCC offers C++20 syntax as an extension, and Clang seems to follow along, so maybe I'm overthinking) IntT immediate; - const auto source = [&]() -> IntT& { - return *resolve( + const auto source_r = [&]() -> IntT& { + return *resolve( instruction, instruction.source().source(), instruction.source(), - registers, - memory, + context, nullptr, &immediate); }; - const auto destination = [&]() -> IntT& { - return *resolve( + const auto source_rmw = [&]() -> IntT& { + return *resolve( + instruction, + instruction.source().source(), + instruction.source(), + context, + nullptr, + &immediate); + }; + const auto destination_r = [&]() -> IntT& { + return *resolve( instruction, instruction.destination().source(), instruction.destination(), - registers, - memory, + context, + nullptr, + &immediate); + }; + const auto destination_w = [&]() -> IntT& { + return *resolve( + instruction, + instruction.destination().source(), + instruction.destination(), + context, + nullptr, + &immediate); + }; + const auto destination_rmw = [&]() -> IntT& { + return *resolve( + instruction, + instruction.destination().source(), + instruction.destination(), + context, nullptr, &immediate); }; @@ -1549,16 +1588,15 @@ template < Primitive::jump( condition, instruction.displacement(), - registers, - flow_controller); + context); }; const auto shift_count = [&]() -> uint8_t { - static constexpr uint8_t mask = (model != Model::i8086) ? 0x1f : 0xff; + static constexpr uint8_t mask = (ContextT::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; + default: return context.registers.cl() & mask; } }; @@ -1566,38 +1604,38 @@ template < // 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(); + if constexpr (data_size == DataSize::Byte) return context.registers.ah(); + else if constexpr (data_size == DataSize::Word) return context.registers.dx(); + else if constexpr (data_size == DataSize::DWord) return context.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(); + if constexpr (data_size == DataSize::Byte) return context.registers.al(); + else if constexpr (data_size == DataSize::Word) return context.registers.ax(); + else if constexpr (data_size == DataSize::DWord) return context.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(); + return context.registers.si(); } else { - return registers.esi(); + return context.registers.esi(); } }; const auto eDI = [&]() -> AddressT& { if constexpr (std::is_same_v) { - return registers.di(); + return context.registers.di(); } else { - return registers.edi(); + return context.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(); + return context.registers.cx(); } else { - return registers.ecx(); + return context.registers.ecx(); } }; @@ -1605,7 +1643,7 @@ template < const auto port = [&](Source source) -> uint16_t { switch(source) { case Source::DirectAddress: return instruction.offset(); - default: return registers.dx(); + default: return context.registers.dx(); } }; @@ -1618,12 +1656,12 @@ template < 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::AAA: Primitive::aaa(context.registers.axp(), context); return; + case Operation::AAD: Primitive::aad(context.registers.axp(), instruction.operand(), context); return; + case Operation::AAM: Primitive::aam(context.registers.axp(), instruction.operand(), context); return; + case Operation::AAS: Primitive::aas(context.registers.axp(), context); return; + case Operation::DAA: Primitive::daa(context.registers.al(), context); return; + case Operation::DAS: Primitive::das(context.registers.al(), context); return; case Operation::CBW: Primitive::cbw(pair_low()); return; case Operation::CWD: Primitive::cwd(pair_high(), pair_low()); return; @@ -1631,200 +1669,194 @@ template < case Operation::ESC: case Operation::NOP: return; - case Operation::HLT: flow_controller.halt(); return; - case Operation::WAIT: flow_controller.wait(); return; + case Operation::HLT: context.flow_controller.halt(); return; + case Operation::WAIT: context.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::ADC: Primitive::add(destination_rmw(), source_r(), context); break; + case Operation::ADD: Primitive::add(destination_rmw(), source_r(), context); break; + case Operation::SBB: Primitive::sub(destination_rmw(), source_r(), context); break; + case Operation::SUB: Primitive::sub(destination_rmw(), source_r(), context); break; + case Operation::CMP: Primitive::sub(destination_r(), source_r(), context); return; + case Operation::TEST: Primitive::test(destination_r(), source_r(), context); return; - 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::MUL: Primitive::mul(pair_high(), pair_low(), source_r(), context); return; + case Operation::IMUL_1: Primitive::imul(pair_high(), pair_low(), source_r(), context); return; + case Operation::DIV: Primitive::div(pair_high(), pair_low(), source_r(), context); return; + case Operation::IDIV: Primitive::idiv(pair_high(), pair_low(), source_r(), context); return; - case Operation::INC: Primitive::inc(destination(), status); break; - case Operation::DEC: Primitive::dec(destination(), status); break; + case Operation::INC: Primitive::inc(destination_rmw(), context); break; + case Operation::DEC: Primitive::dec(destination_rmw(), context); 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::AND: Primitive::and_(destination_rmw(), source_r(), context); break; + case Operation::OR: Primitive::or_(destination_rmw(), source_r(), context); break; + case Operation::XOR: Primitive::xor_(destination_rmw(), source_r(), context); break; + case Operation::NEG: Primitive::neg(source_rmw(), context); break; // TODO: should be a destination. + case Operation::NOT: Primitive::not_(source_rmw()); break; // TODO: should be a destination. - 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::CALLrel: Primitive::call_relative(instruction.displacement(), context); return; + case Operation::CALLabs: Primitive::call_absolute(destination_r(), context); return; + case Operation::CALLfar: Primitive::call_far(instruction, context); 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::JMPrel: jcc(true); return; + case Operation::JMPabs: Primitive::jump_absolute(destination_r(), context); return; + case Operation::JMPfar: Primitive::jump_far(instruction, context); 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::JCXZ: jcc(!eCX()); return; + case Operation::LOOP: Primitive::loop(eCX(), instruction.offset(), context); return; + case Operation::LOOPE: Primitive::loope(eCX(), instruction.offset(), context); return; + case Operation::LOOPNE: Primitive::loopne(eCX(), instruction.offset(), context); 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::IRET: Primitive::iret(context); return; + case Operation::RETnear: Primitive::ret_near(instruction, context); return; + case Operation::RETfar: Primitive::ret_far(instruction, context); return; - case Operation::INT: Primitive::int_(instruction.operand(), flow_controller); return; - case Operation::INTO: Primitive::into(status, flow_controller); return; + case Operation::INT: interrupt(instruction.operand(), context); return; + case Operation::INTO: Primitive::into(context); return; - case Operation::SAHF: Primitive::sahf(registers.ah(), status); return; - case Operation::LAHF: Primitive::lahf(registers.ah(), status); return; + case Operation::SAHF: Primitive::sahf(context.registers.ah(), context); return; + case Operation::LAHF: Primitive::lahf(context.registers.ah(), context); 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::LDS: if constexpr (data_size == DataSize::Word) Primitive::ld(instruction, destination_w(), context); return; + case Operation::LES: if constexpr (data_size == DataSize::Word) Primitive::ld(instruction, destination_w(), context); return; - case Operation::LEA: Primitive::lea(instruction, destination(), memory, registers); return; - case Operation::MOV: Primitive::mov(destination(), source()); return; + case Operation::LEA: Primitive::lea(instruction, destination_w(), context); return; + case Operation::MOV: Primitive::mov(destination_w(), source_r()); break; - 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::JO: jcc(context.status.template condition()); return; + case Operation::JNO: jcc(!context.status.template condition()); return; + case Operation::JB: jcc(context.status.template condition()); return; + case Operation::JNB: jcc(!context.status.template condition()); return; + case Operation::JZ: jcc(context.status.template condition()); return; + case Operation::JNZ: jcc(!context.status.template condition()); return; + case Operation::JBE: jcc(context.status.template condition()); return; + case Operation::JNBE: jcc(!context.status.template condition()); return; + case Operation::JS: jcc(context.status.template condition()); return; + case Operation::JNS: jcc(!context.status.template condition()); return; + case Operation::JP: jcc(!context.status.template condition()); return; + case Operation::JNP: jcc(context.status.template condition()); return; + case Operation::JL: jcc(context.status.template condition()); return; + case Operation::JNL: jcc(!context.status.template condition()); return; + case Operation::JLE: jcc(context.status.template condition()); return; + case Operation::JNLE: jcc(!context.status.template 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::RCL: Primitive::rcl(destination_rmw(), shift_count(), context); break; + case Operation::RCR: Primitive::rcr(destination_rmw(), shift_count(), context); break; + case Operation::ROL: Primitive::rol(destination_rmw(), shift_count(), context); break; + case Operation::ROR: Primitive::ror(destination_rmw(), shift_count(), context); break; + case Operation::SAL: Primitive::sal(destination_rmw(), shift_count(), context); break; + case Operation::SAR: Primitive::sar(destination_rmw(), shift_count(), context); break; + case Operation::SHR: Primitive::shr(destination_rmw(), shift_count(), context); 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::CLC: Primitive::clc(context); return; + case Operation::CLD: Primitive::cld(context); return; + case Operation::CLI: Primitive::cli(context); return; + case Operation::STC: Primitive::stc(context); return; + case Operation::STD: Primitive::std(context); return; + case Operation::STI: Primitive::sti(context); return; + case Operation::CMC: Primitive::cmc(context); return; - case Operation::XCHG: Primitive::xchg(destination(), source()); return; + case Operation::XCHG: Primitive::xchg(destination_rmw(), source_rmw()); break; - case Operation::SALC: Primitive::salc(registers.al(), status); return; + case Operation::SALC: Primitive::salc(context.registers.al(), context); return; case Operation::SETMO: - if constexpr (model == Model::i8086) { - Primitive::setmo(destination(), status); + if constexpr (ContextT::model == Model::i8086) { + Primitive::setmo(destination_w(), context); + break; } else { // TODO. } return; case Operation::SETMOC: - if constexpr (model == Model::i8086) { - Primitive::setmoc(destination(), registers.cl(), status); + if constexpr (ContextT::model == Model::i8086) { + // Test CL out here to avoid taking a reference to memory if + // no write is going to occur. + if(context.registers.cl()) { + Primitive::setmo(destination_w(), context); + } + break; } 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::OUT: Primitive::out(port(instruction.destination().source()), pair_low(), context); return; + case Operation::IN: Primitive::in(port(instruction.source().source()), pair_low(), context); return; - case Operation::XLAT: Primitive::xlat(instruction, memory, registers); return; + case Operation::XLAT: Primitive::xlat(instruction, context); 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::POP: destination_w() = Primitive::pop(context); break; + case Operation::PUSH: Primitive::push(source_r(), context); break; + case Operation::POPF: Primitive::popf(context); break; + case Operation::PUSHF: Primitive::pushf(context); break; case Operation::CMPS: - Primitive::cmps(instruction, eCX(), eSI(), eDI(), memory, status, flow_controller); + Primitive::cmps(instruction, eCX(), eSI(), eDI(), context); break; case Operation::CMPS_REPE: - Primitive::cmps(instruction, eCX(), eSI(), eDI(), memory, status, flow_controller); + Primitive::cmps(instruction, eCX(), eSI(), eDI(), context); break; case Operation::CMPS_REPNE: - Primitive::cmps(instruction, eCX(), eSI(), eDI(), memory, status, flow_controller); + Primitive::cmps(instruction, eCX(), eSI(), eDI(), context); break; case Operation::SCAS: - Primitive::scas(eCX(), eDI(), pair_low(), memory, status, flow_controller); + Primitive::scas(eCX(), eDI(), pair_low(), context); break; case Operation::SCAS_REPE: - Primitive::scas(eCX(), eDI(), pair_low(), memory, status, flow_controller); + Primitive::scas(eCX(), eDI(), pair_low(), context); break; case Operation::SCAS_REPNE: - Primitive::scas(eCX(), eDI(), pair_low(), memory, status, flow_controller); + Primitive::scas(eCX(), eDI(), pair_low(), context); break; case Operation::LODS: - Primitive::lods(instruction, eCX(), eSI(), pair_low(), memory, status, flow_controller); + Primitive::lods(instruction, eCX(), eSI(), pair_low(), context); break; case Operation::LODS_REP: - Primitive::lods(instruction, eCX(), eSI(), pair_low(), memory, status, flow_controller); + Primitive::lods(instruction, eCX(), eSI(), pair_low(), context); break; case Operation::MOVS: - Primitive::movs(instruction, eCX(), eSI(), eDI(), memory, status, flow_controller); + Primitive::movs(instruction, eCX(), eSI(), eDI(), context); break; case Operation::MOVS_REP: - Primitive::movs(instruction, eCX(), eSI(), eDI(), memory, status, flow_controller); + Primitive::movs(instruction, eCX(), eSI(), eDI(), context); break; case Operation::STOS: - Primitive::stos(eCX(), eDI(), pair_low(), memory, status, flow_controller); + Primitive::stos(eCX(), eDI(), pair_low(), context); break; case Operation::STOS_REP: - Primitive::stos(eCX(), eDI(), pair_low(), memory, status, flow_controller); + Primitive::stos(eCX(), eDI(), pair_low(), context); break; case Operation::OUTS: - Primitive::outs(instruction, eCX(), registers.dx(), eSI(), memory, io, status, flow_controller); + Primitive::outs(instruction, eCX(), context.registers.dx(), eSI(), context); break; case Operation::OUTS_REP: - Primitive::outs(instruction, eCX(), registers.dx(), eSI(), memory, io, status, flow_controller); + Primitive::outs(instruction, eCX(), context.registers.dx(), eSI(), context); break; case Operation::INS: - Primitive::ins(eCX(), registers.dx(), eDI(), memory, io, status, flow_controller); + Primitive::ins(eCX(), context.registers.dx(), eDI(), context); break; case Operation::INS_REP: - Primitive::ins(eCX(), registers.dx(), eDI(), memory, io, status, flow_controller); + Primitive::ins(eCX(), context.registers.dx(), eDI(), context); break; } // Write to memory if required to complete this operation. - memory.template write_back(); + // + // TODO: can I eliminate this with some RAII magic? + context.memory.template write_back(); } template < - Model model, typename InstructionT, - typename FlowControllerT, - typename RegistersT, - typename MemoryT, - typename IOT + typename ContextT > void perform( const InstructionT &instruction, - Status &status, - FlowControllerT &flow_controller, - RegistersT ®isters, - MemoryT &memory, - IOT &io + ContextT &context ) { auto size = [](DataSize operation_size, AddressSize address_size) constexpr -> int { return int(operation_size) + (int(address_size) << 2); @@ -1834,10 +1866,10 @@ template < 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); + perform(instruction, context); return; case size(DataSize::Word, AddressSize::b16): - perform(instruction, status, flow_controller, registers, memory, io); + perform(instruction, context); return; // 32-bit combinations. @@ -1846,26 +1878,26 @@ template < // 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); + if constexpr (is_32bit(ContextT::model)) { + perform(instruction, context); return; } break; case size(DataSize::Word, AddressSize::b32): - if constexpr (is_32bit(model)) { - perform(instruction, status, flow_controller, registers, memory, io); + if constexpr (is_32bit(ContextT::model)) { + perform(instruction, context); return; } break; case size(DataSize::DWord, AddressSize::b16): - if constexpr (is_32bit(model)) { - perform(instruction, status, flow_controller, registers, memory, io); + if constexpr (is_32bit(ContextT::model)) { + perform(instruction, context); return; } break; case size(DataSize::DWord, AddressSize::b32): - if constexpr (is_32bit(model)) { - perform(instruction, status, flow_controller, registers, memory, io); + if constexpr (is_32bit(ContextT::model)) { + perform(instruction, context); return; } break; @@ -1878,6 +1910,31 @@ template < assert(false); } +template < + typename ContextT +> void interrupt( + int index, + ContextT &context +) { + const uint32_t address = static_cast(index) << 2; + context.memory.preauthorise_read(address, sizeof(uint16_t) * 2); + context.memory.preauthorise_stack_write(sizeof(uint16_t) * 3); + + const uint16_t ip = context.memory.template access(address); + const uint16_t cs = context.memory.template access(address + 2); + + auto flags = context.status.get(); + Primitive::push(flags, context); + context.status.template set_from(0); + + // Push CS and IP. + Primitive::push(context.registers.cs(), context); + Primitive::push(context.registers.ip(), context); + + // Set new destination. + context.flow_controller.jump(cs, ip); +} + } #endif /* PerformImplementation_h */ diff --git a/InstructionSets/x86/Instruction.hpp b/InstructionSets/x86/Instruction.hpp index 1d2507e55..86326657f 100644 --- a/InstructionSets/x86/Instruction.hpp +++ b/InstructionSets/x86/Instruction.hpp @@ -163,7 +163,7 @@ enum class Operation: uint8_t { XOR, /// NOP; no further fields. NOP, - /// POP from the stack to source. + /// POP from the stack to destination. POP, /// POP from the stack to the flags register. POPF, diff --git a/InstructionSets/x86/Perform.hpp b/InstructionSets/x86/Perform.hpp index 02aeecde3..b72b7cf58 100644 --- a/InstructionSets/x86/Perform.hpp +++ b/InstructionSets/x86/Perform.hpp @@ -15,24 +15,59 @@ namespace InstructionSet::x86 { +/// Explains the type of access that `perform` intends to perform; is provided as a template parameter to whatever +/// the caller supplies as `MemoryT` and `RegistersT` when obtaining a reference to whatever the processor +/// intends to reference. +/// +/// `perform` guarantees to validate all accesses before modifying any state, giving the caller opportunity to generate +/// any exceptions that might be applicable. +enum class AccessType { + /// The requested value will be read from. + Read, + /// The requested value will be written to. + Write, + /// The requested value will be read from and then written to. + ReadModifyWrite, + /// The requested value has already been authorised for whatever form of access is now intended, so there's no + /// need further to inspect. This is done e.g. by operations that will push multiple values to the stack to verify that + /// all necessary stack space is available ahead of pushing anything, though each individual push will then result in + /// a further `Preauthorised` access. + PreauthorisedRead, + PreauthorisedWrite, +}; + +template < + Model model_, + typename FlowControllerT, + typename RegistersT, + typename MemoryT, + typename IOT +> struct ExecutionContext { + FlowControllerT flow_controller; + Status status; + RegistersT registers; + MemoryT memory; + IOT io; + static constexpr Model model = model_; +}; + /// 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 + typename ContextT > void perform( const InstructionT &instruction, - Status &status, - FlowControllerT &flow_controller, - RegistersT ®isters, - MemoryT &memory, - IOT &io + ContextT &context +); + +template < + typename ContextT +> void interrupt( + int index, + ContextT &context ); } diff --git a/OSBindings/Mac/Clock SignalTests/8088Tests.mm b/OSBindings/Mac/Clock SignalTests/8088Tests.mm index f89ceb692..f5621db6c 100644 --- a/OSBindings/Mac/Clock SignalTests/8088Tests.mm +++ b/OSBindings/Mac/Clock SignalTests/8088Tests.mm @@ -14,6 +14,8 @@ #include #include #include +#include +#include #include "NSData+dataWithContentsOfGZippedFile.h" @@ -91,110 +93,193 @@ struct Registers { } }; struct Memory { - enum class Tag { - Seeded, - AccessExpected, - Accessed, - FlagsL, - FlagsH - }; + public: + using AccessType = InstructionSet::x86::AccessType; - std::unordered_map tags; - std::vector memory; - const Registers ®isters_; + template struct ReturnType; - Memory(Registers ®isters) : registers_(registers) { - memory.resize(1024*1024); - } + // Reads: return a value directly. + template struct ReturnType { using type = IntT; }; + template struct ReturnType { using type = IntT; }; - void clear() { - tags.clear(); - } + // Writes: return a reference. + template struct ReturnType { using type = IntT &; }; + template struct ReturnType { using type = IntT &; }; + template struct ReturnType { using type = IntT &; }; - 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; + // Constructor. + Memory(Registers ®isters) : registers_(registers) { + memory.resize(1024*1024); } - 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); - } + // Initialisation. + void clear() { + tags.clear(); + } - // 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; + void seed(uint32_t address, uint8_t value) { + memory[address] = value; + tags[address] = Tag::Seeded; + } + + void touch(uint32_t address) { + tags[address] = Tag::AccessExpected; + } + + // Preauthorisation call-ins. + void preauthorise_stack_write(uint32_t length) { + uint16_t sp = registers_.sp_; + while(length--) { + --sp; + preauthorise(InstructionSet::x86::Source::SS, sp); + } + } + void preauthorise_stack_read(uint32_t length) { + uint16_t sp = registers_.sp_; + while(length--) { + preauthorise(InstructionSet::x86::Source::SS, sp); + ++sp; + } + } + void preauthorise_read(InstructionSet::x86::Source segment, uint16_t start, uint32_t length) { + while(length--) { + preauthorise(segment, start); + ++start; + } + } + void preauthorise_read(uint32_t start, uint32_t length) { + while(length--) { + preauthorise(start); + ++start; + } + } + + // Access call-ins. + + // Accesses an address based on segment:offset. + template + typename ReturnType::type &access(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_; - } else { - address &= 0xf'ffff; + } + } + auto &value = access(segment, address, Tag::Accessed); + + // If the CPU has indicated a write, it should be safe to fuzz the value now. + if(type == AccessType::Write) { + value = IntT(~0); + } + + return value; + } + + // Accesses an address based on physical location. + template + typename ReturnType::type &access(uint32_t address) { + return access(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; } } } - if(tags.find(address) == tags.end()) { - printf("Access to unexpected RAM address"); - } - tags[address] = tag; - return *reinterpret_cast(&memory[address]); - } + private: + enum class Tag { + Seeded, + AccessExpected, + Accessed, + }; - // 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_; + std::unordered_set preauthorisations; + std::unordered_map tags; + std::vector memory; + const Registers ®isters_; + + void preauthorise(uint32_t address) { + preauthorisations.insert(address); + } + void preauthorise(InstructionSet::x86::Source segment, uint16_t address) { + preauthorise((segment_base(segment) + address) & 0xf'ffff); + } + bool test_preauthorisation(uint32_t address) { + auto authorisation = preauthorisations.find(address); + if(authorisation == preauthorisations.end()) { + return false; } + preauthorisations.erase(authorisation); + return true; } - 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; + 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; } - } - 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_; + + // 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 + typename ReturnType::type &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 + typename ReturnType::type &access(uint32_t address, Tag tag) { + if constexpr (type == AccessType::PreauthorisedRead || type == AccessType::PreauthorisedWrite) { + if(!test_preauthorisation(address)) { + printf("Non preauthorised access\n"); + } + } + + // Check for address wraparound + if(address > 0x10'0000 - sizeof(IntT)) { + if constexpr (std::is_same_v) { + address &= 0xf'ffff; + } else { + address &= 0xf'ffff; + 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_; + } + } + } + + if(tags.find(address) == tags.end()) { + printf("Access to unexpected RAM address\n"); + } + tags[address] = tag; + return *reinterpret_cast(&memory[address]); + } + + 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) {} @@ -205,39 +290,7 @@ class FlowController { 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); - } - + // Requirements for perform. void jump(uint16_t address) { registers_.ip_ = address; } @@ -250,12 +303,14 @@ class FlowController { void halt() {} void wait() {} - void begin_instruction() { - should_repeat_ = false; - } void repeat_last() { should_repeat_ = true; } + + // Other actions. + void begin_instruction() { + should_repeat_ = false; + } bool should_repeat() const { return should_repeat_; } @@ -265,23 +320,6 @@ class FlowController { 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 { @@ -290,8 +328,9 @@ struct ExecutionSupport { Memory memory; FlowController flow_controller; IO io; + static constexpr auto model = InstructionSet::x86::Model::i8086; - ExecutionSupport() : memory(registers), flow_controller(memory, registers, status) {} + ExecutionSupport(): memory(registers), flow_controller(memory, registers, status) {} void clear() { memory.clear(); @@ -310,8 +349,8 @@ struct FailedExecution { @end @implementation i8088Tests { - ExecutionSupport execution_support; std::vector execution_failures; + ExecutionSupport execution_support; } - (NSArray *)testFiles { @@ -383,6 +422,8 @@ struct FailedExecution { return hexInstruction; }; + EACCES; + const auto decoded = decoder.decode(data.data(), data.size()); const bool sizeMatched = decoded.first == data.size(); if(assert) { @@ -522,13 +563,9 @@ struct FailedExecution { execution_support.registers.ip_ += decoded.first; do { execution_support.flow_controller.begin_instruction(); - InstructionSet::x86::perform( + InstructionSet::x86::perform( decoded.second, - execution_support.status, - execution_support.flow_controller, - execution_support.registers, - execution_support.memory, - execution_support.io + execution_support ); } while (execution_support.flow_controller.should_repeat()); @@ -537,21 +574,32 @@ struct FailedExecution { InstructionSet::x86::Status intended_status; bool ramEqual = true; + int mask_position = 0; for(NSArray *ram in final_state[@"ram"]) { const uint32_t address = [ram[0] intValue]; + const auto value = execution_support.memory.access(address); - 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((mask_position != 1) && value == [ram[1] intValue]) { + continue; + } + + // Consider whether this apparent mismatch might be because flags have been written to memory; + // allow only one use of the [16-bit] mask per test. + bool matched_with_mask = false; + while(mask_position < 2) { + const uint8_t mask = mask_position ? (flags_mask >> 8) : (flags_mask & 0xff); + ++mask_position; + if((value & mask) == ([ram[1] intValue] & mask)) { + matched_with_mask = true; + break; } } - - if((execution_support.memory.memory[address] & mask) != ([ram[1] intValue] & mask)) { - ramEqual = false; + if(matched_with_mask) { + continue; } + + ramEqual = false; + break; } [self populate:intended_registers status:intended_status value:final_state[@"regs"]]; @@ -663,7 +711,15 @@ struct FailedExecution { } // Lock in current failure rate. - XCTAssertLessThanOrEqual(execution_failures.size(), 1654); + XCTAssertLessThanOrEqual(execution_failures.size(), 4138); + + // Current accepted failures: + // * 65 instances of DAA with invalid BCD input, and 64 of DAS; + // * 2484 instances of LEA from a register, which officially has undefined results; + // * 42 instances of AAM 00h for which I can't figure out what to do with flags; and + // * 1486 instances of IDIV, most either with a rep or repne that on the 8086 specifically negatives the result, + // but some admittedly still unexplained (primarily setting overflow even though the result doesn't overflow; + // a couple of online 8086 emulators also didn't throw so maybe this is an 8086 quirk?) for(const auto &failure: execution_failures) { NSLog(@"Failed %s — %s", failure.test_name.c_str(), failure.reason.c_str());