From 3ee0fcaaeb903a41e03415a3c4ba974876447b46 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 28 Oct 2023 15:56:37 -0400 Subject: [PATCH 01/20] Hatch an appropriate enum. --- InstructionSets/x86/Perform.hpp | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/InstructionSets/x86/Perform.hpp b/InstructionSets/x86/Perform.hpp index 02aeecde3..7367bc9fa 100644 --- a/InstructionSets/x86/Perform.hpp +++ b/InstructionSets/x86/Perform.hpp @@ -15,6 +15,26 @@ 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. + PreAuthorised, +}; + /// 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. /// From 7a886f938a4d4de633a81a605954db22bc9aeef1 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 29 Oct 2023 14:33:39 -0400 Subject: [PATCH 02/20] Propagate access types, even if incorrect. --- .../Implementation/PerformImplementation.hpp | 118 +++++++++--------- OSBindings/Mac/Clock SignalTests/8088Tests.mm | 22 ++-- 2 files changed, 72 insertions(+), 68 deletions(-) diff --git a/InstructionSets/x86/Implementation/PerformImplementation.hpp b/InstructionSets/x86/Implementation/PerformImplementation.hpp index 5d840289e..ae1458497 100644 --- a/InstructionSets/x86/Implementation/PerformImplementation.hpp +++ b/InstructionSets/x86/Implementation/PerformImplementation.hpp @@ -18,7 +18,7 @@ namespace InstructionSet::x86 { -template +template IntT *resolve( InstructionT &instruction, Source source, @@ -29,7 +29,7 @@ IntT *resolve( IntT *immediate = nullptr ); -template +template uint32_t address( InstructionT &instruction, DataPointer pointer, @@ -43,7 +43,7 @@ uint32_t address( uint32_t address; uint16_t zero = 0; - address = *resolve(instruction, pointer.index(), pointer, registers, memory, &zero); + address = *resolve(instruction, pointer.index(), pointer, registers, memory, &zero); if constexpr (is_32bit(model)) { address <<= pointer.scale(); } @@ -52,10 +52,10 @@ 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, registers, memory); } -template +template IntT *register_(RegistersT ®isters) { switch(source) { case Source::eAX: @@ -107,30 +107,32 @@ IntT *register_(RegistersT ®isters) { } } -template +template uint32_t address( InstructionT &instruction, DataPointer pointer, RegistersT ®isters, MemoryT &memory ) { + // 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_(registers); + case Source::eCX: return *register_(registers); + case Source::eDX: return *register_(registers); + case Source::eBX: return *register_(registers); + case Source::eSPorAH: return *register_(registers); + case Source::eBPorCH: return *register_(registers); + case Source::eSIorDH: return *register_(registers); + case Source::eDIorBH: return *register_(registers); + case Source::Indirect: return address(instruction, pointer, registers, memory); + case Source::IndirectNoBase: return address(instruction, pointer, registers, memory); + case Source::DirectAddress: return address(instruction, pointer, registers, memory); } } -template +template IntT *resolve( InstructionT &instruction, Source source, @@ -146,14 +148,14 @@ 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_(registers); + case Source::eCX: return register_(registers); + case Source::eDX: return register_(registers); + case Source::eBX: return register_(registers); + case Source::eSPorAH: return register_(registers); + case Source::eBPorCH: return register_(registers); + case Source::eSIorDH: return register_(registers); + case Source::eDIorBH: return register_(registers); // Segment registers are always 16-bit. case Source::ES: if constexpr (std::is_same_v) return ®isters.es(); else return nullptr; @@ -172,19 +174,19 @@ IntT *resolve( case Source::None: return none; case Source::Indirect: - target_address = address(instruction, pointer, registers, memory); + target_address = address(instruction, pointer, registers, memory); break; case Source::IndirectNoBase: - target_address = address(instruction, pointer, registers, memory); + target_address = address(instruction, pointer, registers, memory); break; case Source::DirectAddress: - target_address = address(instruction, pointer, registers, memory); + target_address = address(instruction, pointer, registers, memory); 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 &memory.template access(instruction.data_segment(), target_address); }; namespace Primitive { @@ -194,7 +196,7 @@ namespace Primitive { template void push(IntT &value, MemoryT &memory, RegistersT ®isters) { registers.sp_ -= sizeof(IntT); - memory.template access( + memory.template access( InstructionSet::x86::Source::SS, registers.sp_) = value; memory.template write_back(); @@ -202,7 +204,7 @@ void push(IntT &value, MemoryT &memory, RegistersT ®isters) { template IntT pop(MemoryT &memory, RegistersT ®isters) { - const auto value = memory.template access( + const auto value = memory.template access( InstructionSet::x86::Source::SS, registers.sp_); registers.sp_ += sizeof(IntT); @@ -848,21 +850,21 @@ void call_far(InstructionT &instruction, case Source::Immediate: flow_controller.call(instruction.segment(), instruction.offset()); return; case Source::Indirect: - source_address = address(instruction, pointer, registers, memory); + source_address = address(instruction, pointer, registers, memory); break; case Source::IndirectNoBase: - source_address = address(instruction, pointer, registers, memory); + source_address = address(instruction, pointer, registers, memory); break; case Source::DirectAddress: - source_address = address(instruction, pointer, registers, memory); + 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); + 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); + const uint16_t segment = memory.template access(source_segment, source_address); flow_controller.call(segment, offset); } @@ -880,21 +882,21 @@ void jump_far(InstructionT &instruction, case Source::Immediate: flow_controller.jump(instruction.segment(), instruction.offset()); return; case Source::Indirect: - source_address = address(instruction, pointer, registers, memory); + source_address = address(instruction, pointer, registers, memory); break; case Source::IndirectNoBase: - source_address = address(instruction, pointer, registers, memory); + source_address = address(instruction, pointer, registers, memory); break; case Source::DirectAddress: - source_address = address(instruction, pointer, registers, memory); + 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); + 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); + const uint16_t segment = memory.template access(source_segment, source_address); flow_controller.jump(segment, offset); } @@ -930,14 +932,14 @@ void ld( RegistersT ®isters ) { const auto pointer = instruction.source(); - auto source_address = address(instruction, pointer, registers, memory); + auto source_address = address(instruction, pointer, registers, memory); const Source source_segment = instruction.data_segment(); - destination = memory.template access(source_segment, source_address); + destination = memory.template access(source_segment, source_address); source_address += 2; switch(selector) { - case Source::DS: registers.ds() = memory.template access(source_segment, source_address); break; - case Source::ES: registers.es() = memory.template access(source_segment, source_address); break; + 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; } } @@ -949,7 +951,7 @@ void lea( RegistersT ®isters ) { // TODO: address size. - destination = IntT(address(instruction, instruction.source(), registers, memory)); + destination = IntT(address(instruction, instruction.source(), registers, memory)); } template @@ -963,7 +965,7 @@ void xlat( address = registers.bx() + registers.al(); } - registers.al() = memory.template access(instruction.data_segment(), address); + registers.al() = memory.template access(instruction.data_segment(), address); } template @@ -1403,8 +1405,8 @@ void cmps(const InstructionT &instruction, AddressT &eCX, AddressT &eSI, Address return; } - IntT lhs = memory.template access(instruction.data_segment(), eSI); - const IntT rhs = memory.template access(Source::ES, eDI); + 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); @@ -1419,7 +1421,7 @@ void scas(AddressT &eCX, AddressT &eDI, IntT &eAX, MemoryT &memory, Status &stat return; } - const IntT rhs = memory.template access(Source::ES, eDI); + const IntT rhs = memory.template access(Source::ES, eDI); eDI += status.direction() * sizeof(IntT); Primitive::sub(eAX, rhs, status); @@ -1433,7 +1435,7 @@ void lods(const InstructionT &instruction, AddressT &eCX, AddressT &eSI, IntT &e return; } - eAX = memory.template access(instruction.data_segment(), eSI); + eAX = memory.template access(instruction.data_segment(), eSI); eSI += status.direction() * sizeof(IntT); repeat(status, eCX, flow_controller); @@ -1445,7 +1447,7 @@ void movs(const InstructionT &instruction, AddressT &eCX, AddressT &eSI, Address return; } - memory.template access(Source::ES, eDI) = memory.template access(instruction.data_segment(), eSI); + memory.template access(Source::ES, eDI) = memory.template access(instruction.data_segment(), eSI); eSI += status.direction() * sizeof(IntT); eDI += status.direction() * sizeof(IntT); @@ -1459,7 +1461,7 @@ void stos(AddressT &eCX, AddressT &eDI, IntT &eAX, MemoryT &memory, Status &stat return; } - memory.template access(Source::ES, eDI) = eAX; + memory.template access(Source::ES, eDI) = eAX; eDI += status.direction() * sizeof(IntT); repeat(status, eCX, flow_controller); @@ -1471,7 +1473,7 @@ void outs(const InstructionT &instruction, AddressT &eCX, uint16_t port, Address return; } - io.template out(port, memory.template access(instruction.data_segment(), eSI)); + io.template out(port, memory.template access(instruction.data_segment(), eSI)); eSI += status.direction() * sizeof(IntT); repeat(status, eCX, flow_controller); @@ -1483,7 +1485,7 @@ void ins(AddressT &eCX, uint16_t port, AddressT &eDI, MemoryT &memory, IOT &io, return; } - memory.template access(Source::ES, eDI) = io.template in(port); + memory.template access(Source::ES, eDI) = io.template in(port); eDI += status.direction() * sizeof(IntT); repeat(status, eCX, flow_controller); @@ -1524,7 +1526,7 @@ template < // Establish source() and destination() shorthand to fetch data if necessary. IntT immediate; const auto source = [&]() -> IntT& { - return *resolve( + return *resolve( instruction, instruction.source().source(), instruction.source(), @@ -1534,7 +1536,7 @@ template < &immediate); }; const auto destination = [&]() -> IntT& { - return *resolve( + return *resolve( instruction, instruction.destination().source(), instruction.destination(), diff --git a/OSBindings/Mac/Clock SignalTests/8088Tests.mm b/OSBindings/Mac/Clock SignalTests/8088Tests.mm index f89ceb692..224aeca93 100644 --- a/OSBindings/Mac/Clock SignalTests/8088Tests.mm +++ b/OSBindings/Mac/Clock SignalTests/8088Tests.mm @@ -25,7 +25,7 @@ namespace { // The tests themselves are not duplicated in this repository; // provide their real path here. -constexpr char TestSuiteHome[] = "/Users/tharte/Projects/ProcessorTests/8088/v1"; +constexpr char TestSuiteHome[] = "/Users/thomasharte/Projects/ProcessorTests/8088/v1"; using Status = InstructionSet::x86::Status; struct Registers { @@ -91,6 +91,7 @@ struct Registers { } }; struct Memory { + using AccessType = InstructionSet::x86::AccessType; enum class Tag { Seeded, AccessExpected, @@ -134,14 +135,14 @@ struct Memory { // 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) { + 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); + return access(physical_address, tag); } // An additional entry point for the flow controller; on the original 8086 interrupt vectors aren't relative // to a selector, they're just at an absolute location. - template IntT &access(uint32_t address, Tag tag) { + template IntT &access(uint32_t address, Tag tag) { // Check for address wraparound if(address >= 0x10'0001 - sizeof(IntT)) { if constexpr (std::is_same_v) { @@ -167,7 +168,7 @@ struct Memory { } // Entry point for the 8086; simply notes that memory was accessed. - template IntT &access([[maybe_unused]] InstructionSet::x86::Source segment, uint32_t address) { + 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. @@ -178,7 +179,7 @@ struct Memory { return write_back_value_; } } - return access(segment, address, Tag::Accessed); + return access(segment, address, Tag::Accessed); } template @@ -210,9 +211,10 @@ class FlowController { void did_far_ret() {} void interrupt(int index) { + // TODO: reauthorise and possibly double fault? 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); + 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); @@ -270,13 +272,13 @@ class FlowController { // 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( + memory_.access( InstructionSet::x86::Source::SS, registers_.sp_, is_flags ? Memory::Tag::FlagsH : Memory::Tag::Accessed ) = value >> 8; --registers_.sp_; - memory_.access( + memory_.access( InstructionSet::x86::Source::SS, registers_.sp_, is_flags ? Memory::Tag::FlagsL : Memory::Tag::Accessed From 1cd1bbd26c0a2eaa75fbbff065cd1ee09b3089ab Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 29 Oct 2023 16:19:10 -0400 Subject: [PATCH 03/20] Make a first pass of access types. --- .../Implementation/PerformImplementation.hpp | 93 ++++++++++++------- 1 file changed, 60 insertions(+), 33 deletions(-) diff --git a/InstructionSets/x86/Implementation/PerformImplementation.hpp b/InstructionSets/x86/Implementation/PerformImplementation.hpp index ae1458497..464eccfbb 100644 --- a/InstructionSets/x86/Implementation/PerformImplementation.hpp +++ b/InstructionSets/x86/Implementation/PerformImplementation.hpp @@ -1526,7 +1526,7 @@ template < // Establish source() and destination() shorthand to fetch data if necessary. IntT immediate; const auto source = [&]() -> IntT& { - return *resolve( + return *resolve( instruction, instruction.source().source(), instruction.source(), @@ -1535,8 +1535,33 @@ template < nullptr, &immediate); }; - const auto destination = [&]() -> IntT& { - return *resolve( + + // 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) + const auto destination_r = [&]() -> IntT& { + return *resolve( + instruction, + instruction.destination().source(), + instruction.destination(), + registers, + memory, + nullptr, + &immediate); + }; + const auto destination_w = [&]() -> IntT& { + return *resolve( + instruction, + instruction.destination().source(), + instruction.destination(), + registers, + memory, + nullptr, + &immediate); + }; + const auto destination_rmw = [&]() -> IntT& { + return *resolve( instruction, instruction.destination().source(), instruction.destination(), @@ -1636,39 +1661,39 @@ template < case Operation::HLT: flow_controller.halt(); return; case Operation::WAIT: flow_controller.wait(); return; - case Operation::ADC: Primitive::add(destination(), source(), status); break; - case Operation::ADD: Primitive::add(destination(), source(), status); break; - case Operation::SBB: Primitive::sub(destination(), source(), status); break; - case Operation::SUB: Primitive::sub(destination(), source(), status); break; - case Operation::CMP: Primitive::sub(destination(), source(), status); break; - case Operation::TEST: Primitive::test(destination(), source(), status); break; + case Operation::ADC: Primitive::add(destination_rmw(), source(), status); break; + case Operation::ADD: Primitive::add(destination_rmw(), source(), status); break; + case Operation::SBB: Primitive::sub(destination_rmw(), source(), status); break; + case Operation::SUB: Primitive::sub(destination_rmw(), source(), status); break; + case Operation::CMP: Primitive::sub(destination_rmw(), source(), status); break; + case Operation::TEST: Primitive::test(destination_r(), source(), status); 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::INC: Primitive::inc(destination(), status); break; - case Operation::DEC: Primitive::dec(destination(), status); break; + case Operation::INC: Primitive::inc(destination_rmw(), status); break; + case Operation::DEC: Primitive::dec(destination_rmw(), status); break; - case Operation::AND: Primitive::and_(destination(), source(), status); break; - case Operation::OR: Primitive::or_(destination(), source(), status); break; - case Operation::XOR: Primitive::xor_(destination(), source(), status); break; - case Operation::NEG: Primitive::neg(source(), status); break; - case Operation::NOT: Primitive::not_(source()); break; + case Operation::AND: Primitive::and_(destination_rmw(), source(), status); break; + case Operation::OR: Primitive::or_(destination_rmw(), source(), status); break; + case Operation::XOR: Primitive::xor_(destination_rmw(), source(), status); break; + case Operation::NEG: Primitive::neg(source(), status); break; // TODO: should be a destination. + case Operation::NOT: Primitive::not_(source()); 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); + Primitive::call_absolute(destination_r(), flow_controller); return; case Operation::CALLfar: Primitive::call_far(instruction, flow_controller, registers, memory); return; case Operation::JMPrel: jcc(true); return; - case Operation::JMPabs: Primitive::jump_absolute(destination(), flow_controller); return; + case Operation::JMPabs: Primitive::jump_absolute(destination_r(), flow_controller); return; case Operation::JMPfar: Primitive::jump_far(instruction, flow_controller, registers, memory); return; case Operation::JCXZ: jcc(!eCX()); return; @@ -1686,11 +1711,11 @@ template < case Operation::SAHF: Primitive::sahf(registers.ah(), status); return; case Operation::LAHF: Primitive::lahf(registers.ah(), status); return; - case Operation::LDS: if constexpr (data_size == DataSize::Word) Primitive::ld(instruction, destination(), memory, registers); return; - case Operation::LES: if constexpr (data_size == DataSize::Word) Primitive::ld(instruction, destination(), memory, registers); return; + case Operation::LDS: if constexpr (data_size == DataSize::Word) Primitive::ld(instruction, destination_w(), memory, registers); return; + case Operation::LES: if constexpr (data_size == DataSize::Word) Primitive::ld(instruction, destination_w(), memory, registers); 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(), memory, registers); return; + case Operation::MOV: Primitive::mov(destination_w(), source()); break; case Operation::JO: jcc(status.condition()); return; case Operation::JNO: jcc(!status.condition()); return; @@ -1709,13 +1734,13 @@ template < case Operation::JLE: jcc(status.condition()); return; case Operation::JNLE: jcc(!status.condition()); return; - case Operation::RCL: Primitive::rcl(destination(), shift_count(), status); break; - case Operation::RCR: Primitive::rcr(destination(), shift_count(), status); break; - case Operation::ROL: Primitive::rol(destination(), shift_count(), status); break; - case Operation::ROR: Primitive::ror(destination(), shift_count(), status); break; - case Operation::SAL: Primitive::sal(destination(), shift_count(), status); break; - case Operation::SAR: Primitive::sar(destination(), shift_count(), status); break; - case Operation::SHR: Primitive::shr(destination(), shift_count(), status); break; + case Operation::RCL: Primitive::rcl(destination_rmw(), shift_count(), status); break; + case Operation::RCR: Primitive::rcr(destination_rmw(), shift_count(), status); break; + case Operation::ROL: Primitive::rol(destination_rmw(), shift_count(), status); break; + case Operation::ROR: Primitive::ror(destination_rmw(), shift_count(), status); break; + case Operation::SAL: Primitive::sal(destination_rmw(), shift_count(), status); break; + case Operation::SAR: Primitive::sar(destination_rmw(), shift_count(), status); break; + case Operation::SHR: Primitive::shr(destination_rmw(), shift_count(), status); break; case Operation::CLC: Primitive::clc(status); return; case Operation::CLD: Primitive::cld(status); return; @@ -1725,19 +1750,21 @@ template < case Operation::STI: Primitive::sti(status); return; case Operation::CMC: Primitive::cmc(status); return; - case Operation::XCHG: Primitive::xchg(destination(), source()); return; + case Operation::XCHG: Primitive::xchg(destination_rmw(), source()); break; - case Operation::SALC: Primitive::salc(registers.al(), status); return; + case Operation::SALC: Primitive::salc(registers.al(), status); return; case Operation::SETMO: if constexpr (model == Model::i8086) { - Primitive::setmo(destination(), status); + Primitive::setmo(destination_w(), status); + break; } else { // TODO. } return; case Operation::SETMOC: if constexpr (model == Model::i8086) { - Primitive::setmoc(destination(), registers.cl(), status); + Primitive::setmoc(destination_w(), registers.cl(), status); + break; } else { // TODO. } From 444c5b94b9b6571f0ee8e2d693f26e8b7563792e Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 29 Oct 2023 16:55:07 -0400 Subject: [PATCH 04/20] Add summary of accepted failures. --- OSBindings/Mac/Clock SignalTests/8088Tests.mm | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/OSBindings/Mac/Clock SignalTests/8088Tests.mm b/OSBindings/Mac/Clock SignalTests/8088Tests.mm index 224aeca93..203a1ee9f 100644 --- a/OSBindings/Mac/Clock SignalTests/8088Tests.mm +++ b/OSBindings/Mac/Clock SignalTests/8088Tests.mm @@ -665,7 +665,15 @@ struct FailedExecution { } // Lock in current failure rate. - XCTAssertLessThanOrEqual(execution_failures.size(), 1654); + XCTAssertLessThanOrEqual(execution_failures.size(), 4138); + + // Current accepted failures: + // * 68 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()); From 02af08ffd24e24d72fb62e427381b6b8aaf55205 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 30 Oct 2023 12:32:44 -0400 Subject: [PATCH 05/20] Fix counts. --- OSBindings/Mac/Clock SignalTests/8088Tests.mm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/OSBindings/Mac/Clock SignalTests/8088Tests.mm b/OSBindings/Mac/Clock SignalTests/8088Tests.mm index 203a1ee9f..fc70d4639 100644 --- a/OSBindings/Mac/Clock SignalTests/8088Tests.mm +++ b/OSBindings/Mac/Clock SignalTests/8088Tests.mm @@ -25,7 +25,7 @@ namespace { // The tests themselves are not duplicated in this repository; // provide their real path here. -constexpr char TestSuiteHome[] = "/Users/thomasharte/Projects/ProcessorTests/8088/v1"; +constexpr char TestSuiteHome[] = "/Users/tharte/Projects/ProcessorTests/8088/v1"; using Status = InstructionSet::x86::Status; struct Registers { @@ -668,7 +668,7 @@ struct FailedExecution { XCTAssertLessThanOrEqual(execution_failures.size(), 4138); // Current accepted failures: - // * 68 instances of DAA with invalid BCD input, and 64 of DAS; + // * 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, From 1d479ec2d716502f3f274907a1d058d3d9ba6eb8 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 31 Oct 2023 15:06:19 -0400 Subject: [PATCH 06/20] Ensure that reads can only read, accept that source is sometimes written to. E.g. XCHG. --- InstructionSets/x86/Decoder.cpp | 296 +++++++++--------- .../Implementation/PerformImplementation.hpp | 68 ++-- OSBindings/Mac/Clock SignalTests/8088Tests.mm | 14 +- 3 files changed, 208 insertions(+), 170 deletions(-) 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 464eccfbb..1e77aba08 100644 --- a/InstructionSets/x86/Implementation/PerformImplementation.hpp +++ b/InstructionSets/x86/Implementation/PerformImplementation.hpp @@ -1523,9 +1523,14 @@ template < 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& { + const auto source_r = [&]() -> IntT& { return *resolve( instruction, instruction.source().source(), @@ -1535,11 +1540,16 @@ template < nullptr, &immediate); }; - - // 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) + const auto source_rmw = [&]() -> IntT& { + return *resolve( + instruction, + instruction.source().source(), + instruction.source(), + registers, + memory, + nullptr, + &immediate); + }; const auto destination_r = [&]() -> IntT& { return *resolve( instruction, @@ -1661,26 +1671,26 @@ template < case Operation::HLT: flow_controller.halt(); return; case Operation::WAIT: flow_controller.wait(); return; - case Operation::ADC: Primitive::add(destination_rmw(), source(), status); break; - case Operation::ADD: Primitive::add(destination_rmw(), source(), status); break; - case Operation::SBB: Primitive::sub(destination_rmw(), source(), status); break; - case Operation::SUB: Primitive::sub(destination_rmw(), source(), status); break; - case Operation::CMP: Primitive::sub(destination_rmw(), source(), status); break; - case Operation::TEST: Primitive::test(destination_r(), source(), status); return; + case Operation::ADC: Primitive::add(destination_rmw(), source_r(), status); break; + case Operation::ADD: Primitive::add(destination_rmw(), source_r(), status); break; + case Operation::SBB: Primitive::sub(destination_rmw(), source_r(), status); break; + case Operation::SUB: Primitive::sub(destination_rmw(), source_r(), status); break; + case Operation::CMP: Primitive::sub(destination_rmw(), source_r(), status); return; + case Operation::TEST: Primitive::test(destination_r(), source_r(), status); 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(), status); return; + case Operation::IMUL_1: Primitive::imul(pair_high(), pair_low(), source_r(), status); return; + case Operation::DIV: Primitive::div(pair_high(), pair_low(), source_r(), flow_controller); return; + case Operation::IDIV: Primitive::idiv(pair_high(), pair_low(), source_r(), flow_controller); return; case Operation::INC: Primitive::inc(destination_rmw(), status); break; case Operation::DEC: Primitive::dec(destination_rmw(), status); break; - case Operation::AND: Primitive::and_(destination_rmw(), source(), status); break; - case Operation::OR: Primitive::or_(destination_rmw(), source(), status); break; - case Operation::XOR: Primitive::xor_(destination_rmw(), source(), status); break; - case Operation::NEG: Primitive::neg(source(), status); break; // TODO: should be a destination. - case Operation::NOT: Primitive::not_(source()); break; // TODO: should be a destination. + case Operation::AND: Primitive::and_(destination_rmw(), source_r(), status); break; + case Operation::OR: Primitive::or_(destination_rmw(), source_r(), status); break; + case Operation::XOR: Primitive::xor_(destination_rmw(), source_r(), status); break; + case Operation::NEG: Primitive::neg(source_rmw(), status); 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); @@ -1715,7 +1725,7 @@ template < case Operation::LES: if constexpr (data_size == DataSize::Word) Primitive::ld(instruction, destination_w(), memory, registers); return; case Operation::LEA: Primitive::lea(instruction, destination_w(), memory, registers); return; - case Operation::MOV: Primitive::mov(destination_w(), source()); break; + case Operation::MOV: Primitive::mov(destination_w(), source_r()); break; case Operation::JO: jcc(status.condition()); return; case Operation::JNO: jcc(!status.condition()); return; @@ -1750,9 +1760,9 @@ template < case Operation::STI: Primitive::sti(status); return; case Operation::CMC: Primitive::cmc(status); return; - case Operation::XCHG: Primitive::xchg(destination_rmw(), source()); break; + case Operation::XCHG: Primitive::xchg(destination_rmw(), source_rmw()); break; - case Operation::SALC: Primitive::salc(registers.al(), status); return; + case Operation::SALC: Primitive::salc(registers.al(), status); return; case Operation::SETMO: if constexpr (model == Model::i8086) { Primitive::setmo(destination_w(), status); @@ -1775,10 +1785,10 @@ template < case Operation::XLAT: Primitive::xlat(instruction, memory, registers); return; - case Operation::POP: source() = Primitive::pop(memory, registers); break; - case Operation::PUSH: Primitive::push(source(), memory, registers); break; - case Operation::POPF: Primitive::popf(memory, registers, status); break; - case Operation::PUSHF: Primitive::pushf(memory, registers, status); break; + case Operation::POP: destination_w() = Primitive::pop(memory, registers); break; + case Operation::PUSH: Primitive::push(source_r(), memory, registers); break; + case Operation::POPF: Primitive::popf(memory, registers, status); break; + case Operation::PUSHF: Primitive::pushf(memory, registers, status); break; case Operation::CMPS: Primitive::cmps(instruction, eCX(), eSI(), eDI(), memory, status, flow_controller); diff --git a/OSBindings/Mac/Clock SignalTests/8088Tests.mm b/OSBindings/Mac/Clock SignalTests/8088Tests.mm index fc70d4639..96f4f9d99 100644 --- a/OSBindings/Mac/Clock SignalTests/8088Tests.mm +++ b/OSBindings/Mac/Clock SignalTests/8088Tests.mm @@ -179,7 +179,16 @@ struct Memory { return write_back_value_; } } - return access(segment, address, Tag::Accessed); + auto &value = access(segment, address, Tag::Accessed); + + // For testing purposes: if the CPU indicated it'll only be reading, copy the requested value into a temporary + // location so that any writes will be discarded. + if(type == AccessType::Read) { + *reinterpret_cast(&read_value_) = value; + return *reinterpret_cast(&read_value_); + } + + return value; } template @@ -196,6 +205,7 @@ struct Memory { 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_; + uint16_t read_value_; }; struct IO { template void out([[maybe_unused]] uint16_t port, [[maybe_unused]] IntT value) {} @@ -385,6 +395,8 @@ struct FailedExecution { return hexInstruction; }; + EACCES; + const auto decoded = decoder.decode(data.data(), data.size()); const bool sizeMatched = decoded.first == data.size(); if(assert) { From 724e08d4f1ee3cfc07829fae88164c1e8cb01994 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 31 Oct 2023 15:09:21 -0400 Subject: [PATCH 07/20] Update commentary on semantics. --- InstructionSets/x86/Instruction.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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, From 8be03be529357fde57b1c283304be6ff2e2d2efc Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 31 Oct 2023 20:28:37 -0400 Subject: [PATCH 08/20] Add test of ::Write mode. --- OSBindings/Mac/Clock SignalTests/8088Tests.mm | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/OSBindings/Mac/Clock SignalTests/8088Tests.mm b/OSBindings/Mac/Clock SignalTests/8088Tests.mm index 96f4f9d99..440f7c29d 100644 --- a/OSBindings/Mac/Clock SignalTests/8088Tests.mm +++ b/OSBindings/Mac/Clock SignalTests/8088Tests.mm @@ -25,7 +25,7 @@ namespace { // The tests themselves are not duplicated in this repository; // provide their real path here. -constexpr char TestSuiteHome[] = "/Users/tharte/Projects/ProcessorTests/8088/v1"; +constexpr char TestSuiteHome[] = "/Users/thomasharte/Projects/ProcessorTests/8088/v1"; using Status = InstructionSet::x86::Status; struct Registers { @@ -188,6 +188,11 @@ struct Memory { return *reinterpret_cast(&read_value_); } + // 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; } From 9538491ee908bae75d288e9c73f4e97a382d2979 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 31 Oct 2023 21:55:22 -0400 Subject: [PATCH 09/20] Fix pushes and pops. --- .../Implementation/PerformImplementation.hpp | 34 ++++++++++--------- OSBindings/Mac/Clock SignalTests/8088Tests.mm | 4 ++- 2 files changed, 21 insertions(+), 17 deletions(-) diff --git a/InstructionSets/x86/Implementation/PerformImplementation.hpp b/InstructionSets/x86/Implementation/PerformImplementation.hpp index 1e77aba08..3e24922b0 100644 --- a/InstructionSets/x86/Implementation/PerformImplementation.hpp +++ b/InstructionSets/x86/Implementation/PerformImplementation.hpp @@ -193,18 +193,18 @@ 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 +template void push(IntT &value, MemoryT &memory, RegistersT ®isters) { registers.sp_ -= sizeof(IntT); - memory.template access( + memory.template access( InstructionSet::x86::Source::SS, registers.sp_) = value; memory.template write_back(); } -template +template IntT pop(MemoryT &memory, RegistersT ®isters) { - const auto value = memory.template access( + const auto value = memory.template access( InstructionSet::x86::Source::SS, registers.sp_); registers.sp_ += sizeof(IntT); @@ -903,23 +903,25 @@ void jump_far(InstructionT &instruction, template void iret(RegistersT ®isters, FlowControllerT &flow_controller, MemoryT &memory, Status &status) { // TODO: all modes other than 16-bit real mode. - registers.ip() = pop(memory, registers); - registers.cs() = pop(memory, registers); - status.set(pop(memory, registers)); + memory.preauthorise_stack(sizeof(uint16_t) * 3); + registers.ip() = pop(memory, registers); + registers.cs() = pop(memory, registers); + status.set(pop(memory, registers)); flow_controller.did_iret(); } template void ret_near(InstructionT instruction, RegistersT ®isters, FlowControllerT &flow_controller, MemoryT &memory) { - registers.ip() = pop(memory, registers); + registers.ip() = pop(memory, registers); registers.sp() += instruction.operand(); flow_controller.did_near_ret(); } template void ret_far(InstructionT instruction, RegistersT ®isters, FlowControllerT &flow_controller, MemoryT &memory) { - registers.ip() = pop(memory, registers); - registers.cs() = pop(memory, registers); + memory.preauthorise_stack(sizeof(uint16_t) * 2); + registers.ip() = pop(memory, registers); + registers.cs() = pop(memory, registers); registers.sp() += instruction.operand(); flow_controller.did_far_ret(); } @@ -1368,13 +1370,13 @@ inline void shr(IntT &destination, uint8_t count, Status &status) { template void popf(MemoryT &memory, RegistersT ®isters, Status &status) { - status.set(pop(memory, registers)); + status.set(pop(memory, registers)); } template void pushf(MemoryT &memory, RegistersT ®isters, Status &status) { uint16_t value = status.get(); - push(value, memory, registers); + push(value, memory, registers); } template @@ -1785,10 +1787,10 @@ template < case Operation::XLAT: Primitive::xlat(instruction, memory, registers); return; - case Operation::POP: destination_w() = Primitive::pop(memory, registers); break; - case Operation::PUSH: Primitive::push(source_r(), 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(memory, registers); break; + case Operation::PUSH: Primitive::push(source_r(), memory, registers); break; + case Operation::POPF: Primitive::popf(memory, registers, status); break; + case Operation::PUSHF: Primitive::pushf(memory, registers, status); break; case Operation::CMPS: Primitive::cmps(instruction, eCX(), eSI(), eDI(), memory, status, flow_controller); diff --git a/OSBindings/Mac/Clock SignalTests/8088Tests.mm b/OSBindings/Mac/Clock SignalTests/8088Tests.mm index 440f7c29d..7abe43f10 100644 --- a/OSBindings/Mac/Clock SignalTests/8088Tests.mm +++ b/OSBindings/Mac/Clock SignalTests/8088Tests.mm @@ -25,7 +25,7 @@ namespace { // The tests themselves are not duplicated in this repository; // provide their real path here. -constexpr char TestSuiteHome[] = "/Users/thomasharte/Projects/ProcessorTests/8088/v1"; +constexpr char TestSuiteHome[] = "/Users/tharte/Projects/ProcessorTests/8088/v1"; using Status = InstructionSet::x86::Status; struct Registers { @@ -133,6 +133,8 @@ struct Memory { return physical_address << 4; } + void preauthorise_stack(uint32_t) {} + // 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) { From da2aea94e3a71915b31d4eab24e82c6d7774e970 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 31 Oct 2023 21:58:32 -0400 Subject: [PATCH 10/20] Fix CMPS. --- InstructionSets/x86/Implementation/PerformImplementation.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/InstructionSets/x86/Implementation/PerformImplementation.hpp b/InstructionSets/x86/Implementation/PerformImplementation.hpp index 3e24922b0..0fd1657ee 100644 --- a/InstructionSets/x86/Implementation/PerformImplementation.hpp +++ b/InstructionSets/x86/Implementation/PerformImplementation.hpp @@ -1408,7 +1408,7 @@ void cmps(const InstructionT &instruction, AddressT &eCX, AddressT &eSI, Address } IntT lhs = memory.template access(instruction.data_segment(), eSI); - const IntT rhs = memory.template access(Source::ES, eDI); + const IntT rhs = memory.template access(Source::ES, eDI); eSI += status.direction() * sizeof(IntT); eDI += status.direction() * sizeof(IntT); From 2432396eaa861643a0061f99227064900b377ea1 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 31 Oct 2023 22:04:26 -0400 Subject: [PATCH 11/20] Fix SETMOC. --- .../x86/Implementation/PerformImplementation.hpp | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/InstructionSets/x86/Implementation/PerformImplementation.hpp b/InstructionSets/x86/Implementation/PerformImplementation.hpp index 0fd1657ee..68844f1e6 100644 --- a/InstructionSets/x86/Implementation/PerformImplementation.hpp +++ b/InstructionSets/x86/Implementation/PerformImplementation.hpp @@ -1048,11 +1048,6 @@ void setmo(IntT &destination, Status &status) { status.set_from(destination); } -template -void setmoc(IntT &destination, uint8_t cl, Status &status) { - if(cl) setmo(destination, status); -} - template inline void rcl(IntT &destination, uint8_t count, Status &status) { /* @@ -1775,7 +1770,11 @@ template < return; case Operation::SETMOC: if constexpr (model == Model::i8086) { - Primitive::setmoc(destination_w(), registers.cl(), status); + // Test CL out here to avoid taking a reference to memory if + // no write is going to occur. + if(registers.cl()) { + Primitive::setmo(destination_w(), status); + } break; } else { // TODO. From 430c60111e5676ae4767b1d5a4883fe4bd7eb8a3 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 31 Oct 2023 22:42:39 -0400 Subject: [PATCH 12/20] CMP doesn't write. --- InstructionSets/x86/Implementation/PerformImplementation.hpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/InstructionSets/x86/Implementation/PerformImplementation.hpp b/InstructionSets/x86/Implementation/PerformImplementation.hpp index 68844f1e6..9b97b161e 100644 --- a/InstructionSets/x86/Implementation/PerformImplementation.hpp +++ b/InstructionSets/x86/Implementation/PerformImplementation.hpp @@ -862,6 +862,7 @@ void call_far(InstructionT &instruction, const Source source_segment = instruction.data_segment(); + // TODO: preauthorise reads. 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); @@ -1672,7 +1673,7 @@ template < case Operation::ADD: Primitive::add(destination_rmw(), source_r(), status); break; case Operation::SBB: Primitive::sub(destination_rmw(), source_r(), status); break; case Operation::SUB: Primitive::sub(destination_rmw(), source_r(), status); break; - case Operation::CMP: Primitive::sub(destination_rmw(), source_r(), status); return; + case Operation::CMP: Primitive::sub(destination_r(), source_r(), status); return; case Operation::TEST: Primitive::test(destination_r(), source_r(), status); return; case Operation::MUL: Primitive::mul(pair_high(), pair_low(), source_r(), status); return; From 78df0d19e4f705fdd2e8b395ee4f3c685a63563f Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 1 Nov 2023 10:03:31 -0400 Subject: [PATCH 13/20] Start experimenting with varying return types. --- OSBindings/Mac/Clock SignalTests/8088Tests.mm | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/OSBindings/Mac/Clock SignalTests/8088Tests.mm b/OSBindings/Mac/Clock SignalTests/8088Tests.mm index 7abe43f10..ea651ac1a 100644 --- a/OSBindings/Mac/Clock SignalTests/8088Tests.mm +++ b/OSBindings/Mac/Clock SignalTests/8088Tests.mm @@ -169,8 +169,15 @@ struct Memory { return *reinterpret_cast(&memory[address]); } + template struct ReturnType; + template struct ReturnType { using type = IntT; }; + template struct ReturnType { using type = IntT &; }; + template struct ReturnType { using type = IntT &; }; + template struct ReturnType { using type = IntT &; }; + // Entry point for the 8086; simply notes that memory was accessed. - template IntT &access([[maybe_unused]] InstructionSet::x86::Source segment, uint32_t address) { + 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. @@ -183,13 +190,6 @@ struct Memory { } auto &value = access(segment, address, Tag::Accessed); - // For testing purposes: if the CPU indicated it'll only be reading, copy the requested value into a temporary - // location so that any writes will be discarded. - if(type == AccessType::Read) { - *reinterpret_cast(&read_value_) = value; - return *reinterpret_cast(&read_value_); - } - // If the CPU has indicated a write, it should be safe to fuzz the value now. if(type == AccessType::Write) { value = IntT(~0); @@ -212,7 +212,6 @@ struct Memory { 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_; - uint16_t read_value_; }; struct IO { template void out([[maybe_unused]] uint16_t port, [[maybe_unused]] IntT value) {} From ef83ac855ab35bd52fd46f2991b832f98c4111a2 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 1 Nov 2023 14:11:10 -0400 Subject: [PATCH 14/20] Fix spelling of Preauthorised, think further on return types. --- .../x86/Implementation/PerformImplementation.hpp | 16 ++++++++-------- InstructionSets/x86/Perform.hpp | 4 ++-- OSBindings/Mac/Clock SignalTests/8088Tests.mm | 12 +++++++++++- 3 files changed, 21 insertions(+), 11 deletions(-) diff --git a/InstructionSets/x86/Implementation/PerformImplementation.hpp b/InstructionSets/x86/Implementation/PerformImplementation.hpp index 9b97b161e..d43e381e0 100644 --- a/InstructionSets/x86/Implementation/PerformImplementation.hpp +++ b/InstructionSets/x86/Implementation/PerformImplementation.hpp @@ -193,18 +193,18 @@ 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 +template void push(IntT &value, MemoryT &memory, RegistersT ®isters) { registers.sp_ -= sizeof(IntT); - memory.template access( + memory.template access( InstructionSet::x86::Source::SS, registers.sp_) = value; memory.template write_back(); } -template +template IntT pop(MemoryT &memory, RegistersT ®isters) { - const auto value = memory.template access( + const auto value = memory.template access( InstructionSet::x86::Source::SS, registers.sp_); registers.sp_ += sizeof(IntT); @@ -850,13 +850,13 @@ void call_far(InstructionT &instruction, case Source::Immediate: flow_controller.call(instruction.segment(), instruction.offset()); return; case Source::Indirect: - source_address = address(instruction, pointer, registers, memory); + source_address = address(instruction, pointer, registers, memory); break; case Source::IndirectNoBase: - source_address = address(instruction, pointer, registers, memory); + source_address = address(instruction, pointer, registers, memory); break; case Source::DirectAddress: - source_address = address(instruction, pointer, registers, memory); + source_address = address(instruction, pointer, registers, memory); break; } @@ -954,7 +954,7 @@ void lea( RegistersT ®isters ) { // TODO: address size. - destination = IntT(address(instruction, instruction.source(), registers, memory)); + destination = IntT(address(instruction, instruction.source(), registers, memory)); } template diff --git a/InstructionSets/x86/Perform.hpp b/InstructionSets/x86/Perform.hpp index 7367bc9fa..b8d44fadb 100644 --- a/InstructionSets/x86/Perform.hpp +++ b/InstructionSets/x86/Perform.hpp @@ -31,8 +31,8 @@ enum class AccessType { /// 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. - PreAuthorised, + /// a further `Preauthorised` access. + Preauthorised, }; /// Performs @c instruction querying @c registers and/or @c memory as required, using @c io for port input/output, diff --git a/OSBindings/Mac/Clock SignalTests/8088Tests.mm b/OSBindings/Mac/Clock SignalTests/8088Tests.mm index ea651ac1a..c69e4c74f 100644 --- a/OSBindings/Mac/Clock SignalTests/8088Tests.mm +++ b/OSBindings/Mac/Clock SignalTests/8088Tests.mm @@ -170,10 +170,20 @@ struct Memory { } template struct ReturnType; + + // Reads: return a value directly. template struct ReturnType { using type = IntT; }; + + // Byte writes: return a reference directly to the byte. + template <> struct ReturnType { using type = uint8_t &; }; + template <> struct ReturnType { using type = uint8_t &; }; + template <> struct ReturnType { using type = uint8_t &; }; + + // Larger writes: I'm on the fence here as to the proper approach to latching and writeback here; + // so offered as a separate case but with a conclusion yet to reach. template struct ReturnType { using type = IntT &; }; template struct ReturnType { using type = IntT &; }; - template struct ReturnType { using type = IntT &; }; + template struct ReturnType { using type = IntT &; }; // Entry point for the 8086; simply notes that memory was accessed. template From 097b328075ddc6ad6ae765cd4708bac6fc3cf77c Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 1 Nov 2023 14:31:42 -0400 Subject: [PATCH 15/20] Split the preauthorised tracks. --- .../x86/Implementation/PerformImplementation.hpp | 12 ++++++------ InstructionSets/x86/Perform.hpp | 3 ++- OSBindings/Mac/Clock SignalTests/8088Tests.mm | 5 +++-- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/InstructionSets/x86/Implementation/PerformImplementation.hpp b/InstructionSets/x86/Implementation/PerformImplementation.hpp index d43e381e0..03c7ae25c 100644 --- a/InstructionSets/x86/Implementation/PerformImplementation.hpp +++ b/InstructionSets/x86/Implementation/PerformImplementation.hpp @@ -196,7 +196,7 @@ namespace Primitive { template void push(IntT &value, MemoryT &memory, RegistersT ®isters) { registers.sp_ -= sizeof(IntT); - memory.template access( + memory.template access( InstructionSet::x86::Source::SS, registers.sp_) = value; memory.template write_back(); @@ -204,7 +204,7 @@ void push(IntT &value, MemoryT &memory, RegistersT ®isters) { template IntT pop(MemoryT &memory, RegistersT ®isters) { - const auto value = memory.template access( + const auto value = memory.template access( InstructionSet::x86::Source::SS, registers.sp_); registers.sp_ += sizeof(IntT); @@ -850,13 +850,13 @@ void call_far(InstructionT &instruction, case Source::Immediate: flow_controller.call(instruction.segment(), instruction.offset()); return; case Source::Indirect: - source_address = address(instruction, pointer, registers, memory); + source_address = address(instruction, pointer, registers, memory); break; case Source::IndirectNoBase: - source_address = address(instruction, pointer, registers, memory); + source_address = address(instruction, pointer, registers, memory); break; case Source::DirectAddress: - source_address = address(instruction, pointer, registers, memory); + source_address = address(instruction, pointer, registers, memory); break; } @@ -954,7 +954,7 @@ void lea( RegistersT ®isters ) { // TODO: address size. - destination = IntT(address(instruction, instruction.source(), registers, memory)); + destination = IntT(address(instruction, instruction.source(), registers, memory)); } template diff --git a/InstructionSets/x86/Perform.hpp b/InstructionSets/x86/Perform.hpp index b8d44fadb..58d7d26a9 100644 --- a/InstructionSets/x86/Perform.hpp +++ b/InstructionSets/x86/Perform.hpp @@ -32,7 +32,8 @@ enum class AccessType { /// 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. - Preauthorised, + PreauthorisedRead, + PreauthorisedWrite, }; /// Performs @c instruction querying @c registers and/or @c memory as required, using @c io for port input/output, diff --git a/OSBindings/Mac/Clock SignalTests/8088Tests.mm b/OSBindings/Mac/Clock SignalTests/8088Tests.mm index c69e4c74f..2fefee383 100644 --- a/OSBindings/Mac/Clock SignalTests/8088Tests.mm +++ b/OSBindings/Mac/Clock SignalTests/8088Tests.mm @@ -173,17 +173,18 @@ struct Memory { // Reads: return a value directly. template struct ReturnType { using type = IntT; }; + template struct ReturnType { using type = IntT; }; // Byte writes: return a reference directly to the byte. template <> struct ReturnType { using type = uint8_t &; }; template <> struct ReturnType { using type = uint8_t &; }; - template <> struct ReturnType { using type = uint8_t &; }; + template <> struct ReturnType { using type = uint8_t &; }; // Larger writes: I'm on the fence here as to the proper approach to latching and writeback here; // so offered as a separate case but with a conclusion yet to reach. template struct ReturnType { using type = IntT &; }; template struct ReturnType { using type = IntT &; }; - template struct ReturnType { using type = IntT &; }; + template struct ReturnType { using type = IntT &; }; // Entry point for the 8086; simply notes that memory was accessed. template From bc095bb9cedcef7dbf2dc5fa0890c4fef1c393b5 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 1 Nov 2023 14:49:30 -0400 Subject: [PATCH 16/20] Slim down the flow controller. --- .../Implementation/PerformImplementation.hpp | 60 +++++++++++-------- OSBindings/Mac/Clock SignalTests/8088Tests.mm | 19 +----- 2 files changed, 38 insertions(+), 41 deletions(-) diff --git a/InstructionSets/x86/Implementation/PerformImplementation.hpp b/InstructionSets/x86/Implementation/PerformImplementation.hpp index 03c7ae25c..cee2863ed 100644 --- a/InstructionSets/x86/Implementation/PerformImplementation.hpp +++ b/InstructionSets/x86/Implementation/PerformImplementation.hpp @@ -821,14 +821,16 @@ 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, RegistersT ®isters, MemoryT &memory, FlowControllerT &flow_controller) { + push(registers.ip(), memory, registers); + flow_controller.jump(registers.ip() + offset); } -template -void call_absolute(IntT target, FlowControllerT &flow_controller) { - flow_controller.call(target); +template +void call_absolute(IntT target, RegistersT ®isters, MemoryT &memory, FlowControllerT &flow_controller) { + push(registers.ip(), memory, registers); + flow_controller.jump(target); } template @@ -843,11 +845,18 @@ void call_far(InstructionT &instruction, MemoryT &memory ) { // TODO: eliminate 16-bit assumption below. - uint16_t source_address = 0; + const Source source_segment = instruction.data_segment(); + memory.preauthorise_stack_write(sizeof(uint16_t) * 2); + + uint16_t source_address; const auto pointer = instruction.destination(); switch(pointer.source()) { default: - case Source::Immediate: flow_controller.call(instruction.segment(), instruction.offset()); return; + case Source::Immediate: + push(registers.cs(), memory, registers); + push(registers.ip(), memory, registers); + flow_controller.jump(instruction.segment(), instruction.offset()); + return; case Source::Indirect: source_address = address(instruction, pointer, registers, memory); @@ -860,13 +869,14 @@ void call_far(InstructionT &instruction, break; } - const Source source_segment = instruction.data_segment(); + memory.preauthorise_read(source_segment, source_address, sizeof(uint16_t) * 2); + push(registers.cs(), memory, registers); + push(registers.ip(), memory, registers); - // TODO: preauthorise reads. - const uint16_t offset = memory.template access(source_segment, source_address); + const uint16_t offset = memory.template access(source_segment, source_address); source_address += 2; - const uint16_t segment = memory.template access(source_segment, source_address); - flow_controller.call(segment, offset); + const uint16_t segment = memory.template access(source_segment, source_address); + flow_controller.jump(segment, offset); } template @@ -904,27 +914,27 @@ void jump_far(InstructionT &instruction, template void iret(RegistersT ®isters, FlowControllerT &flow_controller, MemoryT &memory, Status &status) { // TODO: all modes other than 16-bit real mode. - memory.preauthorise_stack(sizeof(uint16_t) * 3); - registers.ip() = pop(memory, registers); - registers.cs() = pop(memory, registers); + memory.preauthorise_stack_read(sizeof(uint16_t) * 3); + const auto ip = pop(memory, registers); + const auto cs = pop(memory, registers); status.set(pop(memory, registers)); - flow_controller.did_iret(); + flow_controller.jump(cs, ip); } template void ret_near(InstructionT instruction, RegistersT ®isters, FlowControllerT &flow_controller, MemoryT &memory) { - registers.ip() = pop(memory, registers); + const auto ip = pop(memory, registers); registers.sp() += instruction.operand(); - flow_controller.did_near_ret(); + flow_controller.jump(ip); } template void ret_far(InstructionT instruction, RegistersT ®isters, FlowControllerT &flow_controller, MemoryT &memory) { - memory.preauthorise_stack(sizeof(uint16_t) * 2); - registers.ip() = pop(memory, registers); - registers.cs() = pop(memory, registers); + memory.preauthorise_stack_read(sizeof(uint16_t) * 2); + const auto ip = pop(memory, registers); + const auto cs = pop(memory, registers); registers.sp() += instruction.operand(); - flow_controller.did_far_ret(); + flow_controller.jump(cs, ip); } template @@ -1691,10 +1701,10 @@ template < case Operation::NOT: Primitive::not_(source_rmw()); break; // TODO: should be a destination. case Operation::CALLrel: - Primitive::call_relative(instruction.displacement(), registers, flow_controller); + Primitive::call_relative(instruction.displacement(), registers, memory, flow_controller); return; case Operation::CALLabs: - Primitive::call_absolute(destination_r(), flow_controller); + Primitive::call_absolute(destination_r(), registers, memory, flow_controller); return; case Operation::CALLfar: Primitive::call_far(instruction, flow_controller, registers, memory); diff --git a/OSBindings/Mac/Clock SignalTests/8088Tests.mm b/OSBindings/Mac/Clock SignalTests/8088Tests.mm index 2fefee383..88623dfe0 100644 --- a/OSBindings/Mac/Clock SignalTests/8088Tests.mm +++ b/OSBindings/Mac/Clock SignalTests/8088Tests.mm @@ -133,7 +133,9 @@ struct Memory { return physical_address << 4; } - void preauthorise_stack(uint32_t) {} + void preauthorise_stack_write(uint32_t) {} + void preauthorise_stack_read(uint32_t) {} + void preauthorise_read(InstructionSet::x86::Source, uint16_t, uint32_t) {} // 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. @@ -233,10 +235,6 @@ 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) { // TODO: reauthorise and possibly double fault? const uint16_t address = static_cast(index) << 2; @@ -256,17 +254,6 @@ class FlowController { registers_.ip_ = new_ip; } - void call(uint16_t address) { - push(registers_.ip_); - jump(address); - } - - void call(uint16_t segment, uint16_t offset) { - push(registers_.cs_); - push(registers_.ip_); - jump(segment, offset); - } - void jump(uint16_t address) { registers_.ip_ = address; } From acb55aa4e2067a508ad297ec22670269dc91c9f6 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 1 Nov 2023 17:03:23 -0400 Subject: [PATCH 17/20] Subsume repetition of arguments into a single context. Albeit that it (temporarily?) loses some context used during test validation. --- .../Implementation/PerformImplementation.hpp | 1157 +++++++++-------- InstructionSets/x86/Perform.hpp | 34 +- OSBindings/Mac/Clock SignalTests/8088Tests.mm | 57 +- 3 files changed, 632 insertions(+), 616 deletions(-) diff --git a/InstructionSets/x86/Implementation/PerformImplementation.hpp b/InstructionSets/x86/Implementation/PerformImplementation.hpp index cee2863ed..e7db1e792 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,93 +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 ) { @@ -148,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(); @@ -174,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( +template +void push(IntT value, ContextT &context) { + context.registers.sp_ -= sizeof(IntT); + context.memory.template access( InstructionSet::x86::Source::SS, - registers.sp_) = value; - memory.template write_back(); + context.registers.sp_) = value; + context.memory.template write_back(); } -template -IntT pop(MemoryT &memory, RegistersT ®isters) { - const auto value = memory.template access( +template +IntT pop(ContextT &context) { + const auto value = context.memory.template access( InstructionSet::x86::Source::SS, - registers.sp_); - registers.sp_ += sizeof(IntT); + context.registers.sp_); + context.registers.sp_ += sizeof(IntT); return value; } @@ -219,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 @@ -237,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; @@ -260,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 *) @@ -278,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 @@ -305,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 ...) @@ -345,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 ...) @@ -396,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); @@ -483,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 @@ -497,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 @@ -516,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 ...) @@ -551,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 *) @@ -594,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); + InstructionSet::x86::interrupt(Interrupt::DivideError, context); return; } @@ -602,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; } @@ -610,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 *) @@ -650,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; } @@ -659,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; } @@ -667,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; */ @@ -678,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 @@ -698,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; */ @@ -735,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; */ @@ -754,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; */ @@ -769,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; */ @@ -784,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 @@ -801,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 @@ -821,164 +824,153 @@ void not_(IntT &destination) { destination = ~destination; } -template -void call_relative(IntT offset, RegistersT ®isters, MemoryT &memory, FlowControllerT &flow_controller) { - push(registers.ip(), memory, registers); - flow_controller.jump(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, RegistersT ®isters, MemoryT &memory, FlowControllerT &flow_controller) { - push(registers.ip(), memory, registers); - flow_controller.jump(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(); - memory.preauthorise_stack_write(sizeof(uint16_t) * 2); + 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(registers.cs(), memory, registers); - push(registers.ip(), memory, registers); - flow_controller.jump(instruction.segment(), instruction.offset()); + 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, 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; } - memory.preauthorise_read(source_segment, source_address, sizeof(uint16_t) * 2); - push(registers.cs(), memory, registers); - push(registers.ip(), memory, registers); + context.memory.preauthorise_read(source_segment, source_address, sizeof(uint16_t) * 2); + push(context.registers.cs(), context); + push(context.registers.ip(), context); - 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.jump(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 -) { +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.jump(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(); - 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.jump(segment, offset); + const uint16_t segment =context. memory.template access(source_segment, source_address); + context.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. - memory.preauthorise_stack_read(sizeof(uint16_t) * 3); - const auto ip = pop(memory, registers); - const auto cs = pop(memory, registers); - status.set(pop(memory, registers)); - flow_controller.jump(cs, ip); + 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) { - const auto ip = pop(memory, registers); - registers.sp() += instruction.operand(); - flow_controller.jump(ip); +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) { - memory.preauthorise_stack_read(sizeof(uint16_t) * 2); - const auto ip = pop(memory, registers); - const auto cs = pop(memory, registers); - registers.sp() += instruction.operand(); - flow_controller.jump(cs, ip); +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); + 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 @@ -986,40 +978,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 @@ -1040,27 +1029,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 -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 @@ -1091,7 +1090,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(): { @@ -1109,14 +1108,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 @@ -1131,8 +1130,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 ); @@ -1154,11 +1153,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 @@ -1198,14 +1197,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 @@ -1245,8 +1244,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() ); } @@ -1307,35 +1306,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; } @@ -1343,46 +1342,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 @@ -1390,8 +1389,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. @@ -1400,133 +1399,129 @@ 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; @@ -1539,52 +1534,47 @@ template < // (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_r = [&]() -> IntT& { - return *resolve( + return *resolve( instruction, instruction.source().source(), instruction.source(), - registers, - memory, + context, nullptr, &immediate); }; const auto source_rmw = [&]() -> IntT& { - return *resolve( + return *resolve( instruction, instruction.source().source(), instruction.source(), - registers, - memory, + context, nullptr, &immediate); }; const auto destination_r = [&]() -> IntT& { - return *resolve( + return *resolve( instruction, instruction.destination().source(), instruction.destination(), - registers, - memory, + context, nullptr, &immediate); }; const auto destination_w = [&]() -> IntT& { - return *resolve( + return *resolve( instruction, instruction.destination().source(), instruction.destination(), - registers, - memory, + context, nullptr, &immediate); }; const auto destination_rmw = [&]() -> IntT& { - return *resolve( + return *resolve( instruction, instruction.destination().source(), instruction.destination(), - registers, - memory, + context, nullptr, &immediate); }; @@ -1594,16 +1584,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; } }; @@ -1611,38 +1600,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(); } }; @@ -1650,7 +1639,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(); } }; @@ -1663,12 +1652,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; @@ -1676,115 +1665,109 @@ 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_rmw(), source_r(), status); break; - case Operation::ADD: Primitive::add(destination_rmw(), source_r(), status); break; - case Operation::SBB: Primitive::sub(destination_rmw(), source_r(), status); break; - case Operation::SUB: Primitive::sub(destination_rmw(), source_r(), status); break; - case Operation::CMP: Primitive::sub(destination_r(), source_r(), status); return; - case Operation::TEST: Primitive::test(destination_r(), source_r(), status); return; + 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_r(), status); return; - case Operation::IMUL_1: Primitive::imul(pair_high(), pair_low(), source_r(), status); return; - case Operation::DIV: Primitive::div(pair_high(), pair_low(), source_r(), flow_controller); return; - case Operation::IDIV: Primitive::idiv(pair_high(), pair_low(), source_r(), 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_rmw(), status); break; - case Operation::DEC: Primitive::dec(destination_rmw(), 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_rmw(), source_r(), status); break; - case Operation::OR: Primitive::or_(destination_rmw(), source_r(), status); break; - case Operation::XOR: Primitive::xor_(destination_rmw(), source_r(), status); break; - case Operation::NEG: Primitive::neg(source_rmw(), status); break; // TODO: should be a destination. + 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, memory, flow_controller); - return; - case Operation::CALLabs: - Primitive::call_absolute(destination_r(), registers, memory, 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_r(), 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_w(), memory, registers); return; - case Operation::LES: if constexpr (data_size == DataSize::Word) Primitive::ld(instruction, destination_w(), 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_w(), memory, registers); return; - case Operation::MOV: Primitive::mov(destination_w(), source_r()); break; + 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_rmw(), shift_count(), status); break; - case Operation::RCR: Primitive::rcr(destination_rmw(), shift_count(), status); break; - case Operation::ROL: Primitive::rol(destination_rmw(), shift_count(), status); break; - case Operation::ROR: Primitive::ror(destination_rmw(), shift_count(), status); break; - case Operation::SAL: Primitive::sal(destination_rmw(), shift_count(), status); break; - case Operation::SAR: Primitive::sar(destination_rmw(), shift_count(), status); break; - case Operation::SHR: Primitive::shr(destination_rmw(), 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_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_w(), status); + if constexpr (ContextT::model == Model::i8086) { + Primitive::setmo(destination_w(), context); break; } else { // TODO. } return; case Operation::SETMOC: - if constexpr (model == Model::i8086) { + 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(registers.cl()) { - Primitive::setmo(destination_w(), status); + if(context.registers.cl()) { + Primitive::setmo(destination_w(), context); } break; } else { @@ -1792,90 +1775,84 @@ template < } 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: destination_w() = Primitive::pop(memory, registers); break; - case Operation::PUSH: Primitive::push(source_r(), 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); @@ -1885,10 +1862,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. @@ -1897,26 +1874,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; @@ -1929,6 +1906,30 @@ 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); + + Primitive::push(context.status.get(), 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/Perform.hpp b/InstructionSets/x86/Perform.hpp index 58d7d26a9..b72b7cf58 100644 --- a/InstructionSets/x86/Perform.hpp +++ b/InstructionSets/x86/Perform.hpp @@ -36,24 +36,38 @@ enum class AccessType { 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 88623dfe0..7507ae9f7 100644 --- a/OSBindings/Mac/Clock SignalTests/8088Tests.mm +++ b/OSBindings/Mac/Clock SignalTests/8088Tests.mm @@ -92,6 +92,19 @@ struct Registers { }; struct Memory { using AccessType = InstructionSet::x86::AccessType; + + template struct ReturnType; + + // Reads: return a value directly. + template struct ReturnType { using type = IntT; }; + template struct ReturnType { using type = IntT; }; + + // Writes: return a reference. + template struct ReturnType { using type = IntT &; }; + template struct ReturnType { using type = IntT &; }; + template struct ReturnType { using type = IntT &; }; + + enum class Tag { Seeded, AccessExpected, @@ -136,17 +149,20 @@ struct Memory { void preauthorise_stack_write(uint32_t) {} void preauthorise_stack_read(uint32_t) {} void preauthorise_read(InstructionSet::x86::Source, uint16_t, uint32_t) {} + void preauthorise_read(uint16_t, uint32_t) {} // 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) { + 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 IntT &access(uint32_t address, Tag tag) { + template + typename ReturnType::type &access(uint32_t address, Tag tag) { // Check for address wraparound if(address >= 0x10'0001 - sizeof(IntT)) { if constexpr (std::is_same_v) { @@ -171,23 +187,6 @@ struct Memory { return *reinterpret_cast(&memory[address]); } - template struct ReturnType; - - // Reads: return a value directly. - template struct ReturnType { using type = IntT; }; - template struct ReturnType { using type = IntT; }; - - // Byte writes: return a reference directly to the byte. - template <> struct ReturnType { using type = uint8_t &; }; - template <> struct ReturnType { using type = uint8_t &; }; - template <> struct ReturnType { using type = uint8_t &; }; - - // Larger writes: I'm on the fence here as to the proper approach to latching and writeback here; - // so offered as a separate case but with a conclusion yet to reach. - template struct ReturnType { using type = IntT &; }; - template struct ReturnType { using type = IntT &; }; - template struct ReturnType { using type = IntT &; }; - // Entry point for the 8086; simply notes that memory was accessed. template typename ReturnType::type &access(InstructionSet::x86::Source segment, uint32_t address) { @@ -211,7 +210,12 @@ struct Memory { return value; } - template + 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) { @@ -306,8 +310,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(); @@ -326,8 +331,8 @@ struct FailedExecution { @end @implementation i8088Tests { - ExecutionSupport execution_support; std::vector execution_failures; + ExecutionSupport execution_support; } - (NSArray *)testFiles { @@ -540,13 +545,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()); From e4fdf0914941fb4f77849729089afb5e9027164a Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 1 Nov 2023 23:39:52 -0400 Subject: [PATCH 18/20] Fix PUSH SP, far call. Further simplify FlowController. --- .../Implementation/PerformImplementation.hpp | 24 +++--- OSBindings/Mac/Clock SignalTests/8088Tests.mm | 75 +++++++------------ 2 files changed, 39 insertions(+), 60 deletions(-) diff --git a/InstructionSets/x86/Implementation/PerformImplementation.hpp b/InstructionSets/x86/Implementation/PerformImplementation.hpp index e7db1e792..0a736e6a9 100644 --- a/InstructionSets/x86/Implementation/PerformImplementation.hpp +++ b/InstructionSets/x86/Implementation/PerformImplementation.hpp @@ -189,13 +189,13 @@ IntT *resolve( 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. +// 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) { +void push(IntT &value, ContextT &context) { context.registers.sp_ -= sizeof(IntT); context.memory.template access( - InstructionSet::x86::Source::SS, + Source::SS, context.registers.sp_) = value; context.memory.template write_back(); } @@ -203,7 +203,7 @@ void push(IntT value, ContextT &context) { template IntT pop(ContextT &context) { const auto value = context.memory.template access( - InstructionSet::x86::Source::SS, + Source::SS, context.registers.sp_); context.registers.sp_ += sizeof(IntT); return value; @@ -597,7 +597,7 @@ void div(IntT &destination_high, IntT &destination_low, IntT source, ContextT &c The CF, OF, SF, ZF, AF, and PF flags are undefined. */ if(!source) { - InstructionSet::x86::interrupt(Interrupt::DivideError, context); + interrupt(Interrupt::DivideError, context); return; } @@ -869,12 +869,14 @@ void call_far(InstructionT &instruction, ContextT &context) { } context.memory.preauthorise_read(source_segment, source_address, sizeof(uint16_t) * 2); - push(context.registers.cs(), context); - push(context.registers.ip(), context); - 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); } @@ -899,6 +901,7 @@ void jump_far(InstructionT &instruction, ContextT &context) { } const Source source_segment = instruction.data_segment(); + 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; @@ -1919,7 +1922,8 @@ template < const uint16_t ip = context.memory.template access(address); const uint16_t cs = context.memory.template access(address + 2); - Primitive::push(context.status.get(), context); + auto flags = context.status.get(); + Primitive::push(flags, context); context.status.template set_from(0); // Push CS and IP. diff --git a/OSBindings/Mac/Clock SignalTests/8088Tests.mm b/OSBindings/Mac/Clock SignalTests/8088Tests.mm index 7507ae9f7..b3ceacae7 100644 --- a/OSBindings/Mac/Clock SignalTests/8088Tests.mm +++ b/OSBindings/Mac/Clock SignalTests/8088Tests.mm @@ -109,8 +109,6 @@ struct Memory { Seeded, AccessExpected, Accessed, - FlagsL, - FlagsH }; std::unordered_map tags; @@ -239,25 +237,7 @@ class FlowController { FlowController(Memory &memory, Registers ®isters, Status &status) : memory_(memory), registers_(registers), status_(status) {} - void interrupt(int index) { - // TODO: reauthorise and possibly double fault? - 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; - } - + // Requirements for perform. void jump(uint16_t address) { registers_.ip_ = address; } @@ -270,12 +250,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_; } @@ -285,23 +267,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 { @@ -556,21 +521,31 @@ 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]; - 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) && execution_support.memory.memory[address] == [ram[1] intValue]) { + continue; } - if((execution_support.memory.memory[address] & mask) != ([ram[1] intValue] & mask)) { - ramEqual = false; + // 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((execution_support.memory.memory[address] & mask) == ([ram[1] intValue] & mask)) { + matched_with_mask = true; + break; + } } + if(matched_with_mask) { + continue; + } + + ramEqual = false; + break; } [self populate:intended_registers status:intended_status value:final_state[@"regs"]]; From 8d0deeb20e7ce54244e538b4b997507b598fe09f Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 2 Nov 2023 14:25:13 -0400 Subject: [PATCH 19/20] Clean up `Memory`. --- OSBindings/Mac/Clock SignalTests/8088Tests.mm | 240 +++++++++--------- 1 file changed, 124 insertions(+), 116 deletions(-) diff --git a/OSBindings/Mac/Clock SignalTests/8088Tests.mm b/OSBindings/Mac/Clock SignalTests/8088Tests.mm index b3ceacae7..3991743a3 100644 --- a/OSBindings/Mac/Clock SignalTests/8088Tests.mm +++ b/OSBindings/Mac/Clock SignalTests/8088Tests.mm @@ -91,142 +91,149 @@ struct Registers { } }; struct Memory { - using AccessType = InstructionSet::x86::AccessType; + public: + using AccessType = InstructionSet::x86::AccessType; - template struct ReturnType; + template struct ReturnType; - // Reads: return a value directly. - template struct ReturnType { using type = IntT; }; - template struct ReturnType { using type = IntT; }; + // Reads: return a value directly. + template struct ReturnType { using type = IntT; }; + template struct ReturnType { using type = IntT; }; - // Writes: return a reference. - template struct ReturnType { using type = IntT &; }; - template struct ReturnType { using type = IntT &; }; - template struct ReturnType { using type = IntT &; }; + // Writes: return a reference. + template struct ReturnType { using type = IntT &; }; + template struct ReturnType { using type = IntT &; }; + template struct ReturnType { using type = IntT &; }; - - enum class Tag { - Seeded, - AccessExpected, - Accessed, - }; - - std::unordered_map tags; - std::vector memory; - const Registers ®isters_; - - Memory(Registers ®isters) : registers_(registers) { - memory.resize(1024*1024); - } - - void clear() { - tags.clear(); - } - - void seed(uint32_t address, uint8_t value) { - memory[address] = value; - tags[address] = Tag::Seeded; - } - - void touch(uint32_t address) { - tags[address] = Tag::AccessExpected; - } - - uint32_t segment_base(InstructionSet::x86::Source segment) { - uint32_t physical_address; - using Source = InstructionSet::x86::Source; - switch(segment) { - default: physical_address = registers_.ds_; break; - case Source::ES: physical_address = registers_.es_; break; - case Source::CS: physical_address = registers_.cs_; break; - case Source::SS: physical_address = registers_.ss_; break; + // Constructor. + Memory(Registers ®isters) : registers_(registers) { + memory.resize(1024*1024); } - return physical_address << 4; - } - void preauthorise_stack_write(uint32_t) {} - void preauthorise_stack_read(uint32_t) {} - void preauthorise_read(InstructionSet::x86::Source, uint16_t, uint32_t) {} - void preauthorise_read(uint16_t, uint32_t) {} + // Initialisation. + void clear() { + tags.clear(); + } - // 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); - } + void seed(uint32_t address, uint8_t value) { + memory[address] = value; + tags[address] = Tag::Seeded; + } - // 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) { - // 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 touch(uint32_t address) { + tags[address] = Tag::AccessExpected; + } + + // Preauthorisation call-ins. + void preauthorise_stack_write(uint32_t) {} + void preauthorise_stack_read(uint32_t) {} + void preauthorise_read(InstructionSet::x86::Source, uint16_t, uint32_t) {} + void preauthorise_read(uint16_t, uint32_t) {} + + // 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 - 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_; + std::unordered_map tags; + std::vector memory; + const Registers ®isters_; + + 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; } - } - 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 physical_address << 4; } - return value; - } - template - typename ReturnType::type &access(uint32_t address) { - return access(address, Tag::Accessed); - } + // 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); + } - 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; + // 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) { + // 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_; + } + } } - } - } - 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_; + if(tags.find(address) == tags.end()) { + printf("Access to unexpected RAM address"); + } + 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) {} @@ -524,8 +531,9 @@ struct FailedExecution { 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); - if((mask_position != 1) && execution_support.memory.memory[address] == [ram[1] intValue]) { + if((mask_position != 1) && value == [ram[1] intValue]) { continue; } @@ -535,7 +543,7 @@ struct FailedExecution { while(mask_position < 2) { const uint8_t mask = mask_position ? (flags_mask >> 8) : (flags_mask & 0xff); ++mask_position; - if((execution_support.memory.memory[address] & mask) == ([ram[1] intValue] & mask)) { + if((value & mask) == ([ram[1] intValue] & mask)) { matched_with_mask = true; break; } From 770803b07333b63b678b607d384e7937bbdae83c Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 2 Nov 2023 15:37:59 -0400 Subject: [PATCH 20/20] Be more careful as to authorisation. --- .../Implementation/PerformImplementation.hpp | 15 ++--- OSBindings/Mac/Clock SignalTests/8088Tests.mm | 56 +++++++++++++++++-- 2 files changed, 59 insertions(+), 12 deletions(-) diff --git a/InstructionSets/x86/Implementation/PerformImplementation.hpp b/InstructionSets/x86/Implementation/PerformImplementation.hpp index 0a736e6a9..3f84adc73 100644 --- a/InstructionSets/x86/Implementation/PerformImplementation.hpp +++ b/InstructionSets/x86/Implementation/PerformImplementation.hpp @@ -903,9 +903,9 @@ void jump_far(InstructionT &instruction, ContextT &context) { const Source source_segment = instruction.data_segment(); context.memory.preauthorise_read(source_segment, source_address, sizeof(uint16_t) * 2); - const uint16_t offset = context.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 =context. memory.template access(source_segment, source_address); + const uint16_t segment = context.memory.template access(source_segment, source_address); context.flow_controller.jump(segment, offset); } @@ -945,11 +945,12 @@ void ld( auto source_address = address(instruction, pointer, context); const Source source_segment = instruction.data_segment(); - destination = context.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: 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; + 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; } } @@ -1919,8 +1920,8 @@ template < 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); + 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); diff --git a/OSBindings/Mac/Clock SignalTests/8088Tests.mm b/OSBindings/Mac/Clock SignalTests/8088Tests.mm index 3991743a3..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" @@ -125,10 +127,32 @@ struct Memory { } // Preauthorisation call-ins. - void preauthorise_stack_write(uint32_t) {} - void preauthorise_stack_read(uint32_t) {} - void preauthorise_read(InstructionSet::x86::Source, uint16_t, uint32_t) {} - void preauthorise_read(uint16_t, uint32_t) {} + 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. @@ -179,10 +203,26 @@ struct Memory { Accessed, }; + 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; + } + uint32_t segment_base(InstructionSet::x86::Source segment) { uint32_t physical_address; using Source = InstructionSet::x86::Source; @@ -208,6 +248,12 @@ struct Memory { // 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) { @@ -225,7 +271,7 @@ struct Memory { } if(tags.find(address) == tags.end()) { - printf("Access to unexpected RAM address"); + printf("Access to unexpected RAM address\n"); } tags[address] = tag; return *reinterpret_cast(&memory[address]);