From 7f6e3cf8b71872629b3658728c3f16a8054a12ac Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 5 Oct 2023 10:51:55 -0400 Subject: [PATCH 001/128] Define the available flags. --- InstructionSets/x86/Status.hpp | 61 +++++++++++++++++++ .../Clock Signal.xcodeproj/project.pbxproj | 2 + 2 files changed, 63 insertions(+) create mode 100644 InstructionSets/x86/Status.hpp diff --git a/InstructionSets/x86/Status.hpp b/InstructionSets/x86/Status.hpp new file mode 100644 index 000000000..8ccb31c6b --- /dev/null +++ b/InstructionSets/x86/Status.hpp @@ -0,0 +1,61 @@ +// +// Status.hpp +// Clock Signal +// +// Created by Thomas Harte on 05/10/2023. +// Copyright © 2023 Thomas Harte. All rights reserved. +// + +#ifndef InstructionSets_x86_Status_hpp +#define InstructionSets_x86_Status_hpp + + +namespace InstructionSet::x86 { + +namespace ConditionCode { + +// +// Standard flags. +// + +static constexpr uint32_t Carry = 1 << 0; +static constexpr uint32_t Parity = 1 << 2; +static constexpr uint32_t AuxiliaryCarry = 1 << 4; +static constexpr uint32_t Zero = 1 << 6; +static constexpr uint32_t Sign = 1 << 7; +static constexpr uint32_t Trap = 1 << 8; +static constexpr uint32_t Interrupt = 1 << 9; +static constexpr uint32_t Direction = 1 << 10; +static constexpr uint32_t Overflow = 1 << 11; + +// +// 80286+ additions. +// + +static constexpr uint32_t IOPrivilege = (1 << 12) | (1 << 13); +static constexpr uint32_t NestedTask = 1 << 14; + +// +// 16-bit protected mode flags. +// + +static constexpr uint32_t ProtectionEnable = 1 << 16; +static constexpr uint32_t MonitorProcessorExtension = 1 << 17; +static constexpr uint32_t ProcessorExtensionExtension = 1 << 18; +static constexpr uint32_t TaskSwitch = 1 << 19; + +// +// 32-bit protected mode flags. +// + +static constexpr uint32_t Resume = 1 << 16; +static constexpr uint32_t VirtualMode = 1 << 17; + +} + +struct Status { +}; + +} + +#endif /* InstructionSets_x86_Status_hpp */ diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index 81c6ba4b8..a79d0850e 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -1126,6 +1126,7 @@ /* Begin PBXFileReference section */ 423BDC492AB24699008E37B6 /* 8088Tests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = 8088Tests.mm; sourceTree = ""; }; + 42437B342ACF02A9006DFED1 /* Status.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Status.hpp; sourceTree = ""; }; 4281572E2AA0334300E16AA1 /* Carry.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Carry.hpp; sourceTree = ""; }; 428168372A16C25C008ECD27 /* LineLayout.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = LineLayout.hpp; sourceTree = ""; }; 428168392A37AFB4008ECD27 /* DispatcherTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = DispatcherTests.mm; sourceTree = ""; }; @@ -4993,6 +4994,7 @@ 4BEDA3B825B25563000C2DBD /* Decoder.hpp */, 4BEDA3DB25B2588F000C2DBD /* Instruction.hpp */, 4BE3C69527CBC540000EAD28 /* Model.hpp */, + 42437B342ACF02A9006DFED1 /* Status.hpp */, ); path = x86; sourceTree = ""; From 01851874ea5d945c8e2f9e9d5e2ddcd62283ab7d Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 5 Oct 2023 11:23:41 -0400 Subject: [PATCH 002/128] I guess this is what a `perform` looks like. --- InstructionSets/x86/Perform.hpp | 43 +++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 InstructionSets/x86/Perform.hpp diff --git a/InstructionSets/x86/Perform.hpp b/InstructionSets/x86/Perform.hpp new file mode 100644 index 000000000..fe84da795 --- /dev/null +++ b/InstructionSets/x86/Perform.hpp @@ -0,0 +1,43 @@ +// +// Perform.hpp +// Clock Signal +// +// Created by Thomas Harte on 05/10/2023. +// Copyright © 2023 Thomas Harte. All rights reserved. +// + +#ifndef Perform_h +#define Perform_h + +#include "Instruction.hpp" + +namespace InstructionSet::x86 { + +/// Performs @c instruction using @c resolver to obtain to query @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. +/// +/// If the template parameter @c operation is not @c Operation::Undefined then that operation will be performed, ignoring +/// whatever is specifed in @c instruction. +template < + Model model, + typename FlowControllerT, + typename DataPointerResolverT, + typename RegistersT, + typename MemoryT, + typename IOT, + Operation operation = Operation::Undefined +> void perform( + const Instruction &instruction, + Status &status, + FlowControllerT &flow_controller, + DataPointerResolverT &resolver, + RegistersT ®isters, + MemoryT &memory, + IOT &io +); + +} + +#endif /* Perform_h */ From 488fceb42bd532a33ed275294e7bad8200099ebc Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 5 Oct 2023 11:23:58 -0400 Subject: [PATCH 003/128] Clean up, add a TODO. --- InstructionSets/x86/DataPointerResolver.hpp | 70 ++++++++++--------- .../Clock Signal.xcodeproj/project.pbxproj | 2 + 2 files changed, 39 insertions(+), 33 deletions(-) diff --git a/InstructionSets/x86/DataPointerResolver.hpp b/InstructionSets/x86/DataPointerResolver.hpp index 056aa9476..306c87e4c 100644 --- a/InstructionSets/x86/DataPointerResolver.hpp +++ b/InstructionSets/x86/DataPointerResolver.hpp @@ -215,44 +215,45 @@ template uint32_t DataPointerResolver::effective_address( RegistersT ®isters, const Instruction &instruction, - DataPointer pointer) { - using AddressT = typename Instruction::AddressT; - AddressT base = 0, index = 0; + DataPointer pointer +) { + using AddressT = typename Instruction::AddressT; + AddressT base = 0, index = 0; - if constexpr (has_base) { - switch(pointer.base()) { - default: break; - ALLREGS(base, false); - } - } - - switch(pointer.index()) { + if constexpr (has_base) { + switch(pointer.base()) { default: break; - ALLREGS(index, false); + ALLREGS(base, false); } - - uint32_t address = index; - if constexpr (model >= Model::i80386) { - address <<= pointer.scale(); - } else { - assert(!pointer.scale()); - } - - // Always compute address as 32-bit. - // TODO: verify use of memory_mask around here. - // Also I think possibly an exception is supposed to be generated - // if the programmer is in 32-bit mode and has asked for 16-bit - // address computation but generated e.g. a 17-bit result. Look into - // that when working on execution. For now the goal is merely decoding - // and this code exists both to verify the presence of all necessary - // fields and to help to explore the best breakdown of storage - // within Instruction. - constexpr uint32_t memory_masks[] = {0x0000'ffff, 0xffff'ffff}; - const uint32_t memory_mask = memory_masks[int(instruction.address_size())]; - address = (address & memory_mask) + (base & memory_mask) + instruction.displacement(); - return address; } + switch(pointer.index()) { + default: break; + ALLREGS(index, false); + } + + uint32_t address = index; + if constexpr (model >= Model::i80386) { + address <<= pointer.scale(); + } else { + assert(!pointer.scale()); + } + + // Always compute address as 32-bit. + // TODO: verify use of memory_mask around here. + // Also I think possibly an exception is supposed to be generated + // if the programmer is in 32-bit mode and has asked for 16-bit + // address computation but generated e.g. a 17-bit result. Look into + // that when working on execution. For now the goal is merely decoding + // and this code exists both to verify the presence of all necessary + // fields and to help to explore the best breakdown of storage + // within Instruction. + constexpr uint32_t memory_masks[] = {0x0000'ffff, 0xffff'ffff}; + const uint32_t memory_mask = memory_masks[int(instruction.address_size())]; + address = (address & memory_mask) + (base & memory_mask) + instruction.displacement(); + return address; +} + template template void DataPointerResolver::access( RegistersT ®isters, @@ -282,6 +283,9 @@ template void DataPointerResolver \ (registers, instruction, pointer); \ diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index a79d0850e..a285bc85d 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -1127,6 +1127,7 @@ /* Begin PBXFileReference section */ 423BDC492AB24699008E37B6 /* 8088Tests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = 8088Tests.mm; sourceTree = ""; }; 42437B342ACF02A9006DFED1 /* Status.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Status.hpp; sourceTree = ""; }; + 42437B352ACF0AA2006DFED1 /* Perform.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Perform.hpp; sourceTree = ""; }; 4281572E2AA0334300E16AA1 /* Carry.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Carry.hpp; sourceTree = ""; }; 428168372A16C25C008ECD27 /* LineLayout.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = LineLayout.hpp; sourceTree = ""; }; 428168392A37AFB4008ECD27 /* DispatcherTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = DispatcherTests.mm; sourceTree = ""; }; @@ -4995,6 +4996,7 @@ 4BEDA3DB25B2588F000C2DBD /* Instruction.hpp */, 4BE3C69527CBC540000EAD28 /* Model.hpp */, 42437B342ACF02A9006DFED1 /* Status.hpp */, + 42437B352ACF0AA2006DFED1 /* Perform.hpp */, ); path = x86; sourceTree = ""; From 524e4ae65c4d9cac908c282964ea8cea62ad53ef Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 5 Oct 2023 11:26:52 -0400 Subject: [PATCH 004/128] Tidy up just slightly more. --- InstructionSets/x86/DataPointerResolver.hpp | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/InstructionSets/x86/DataPointerResolver.hpp b/InstructionSets/x86/DataPointerResolver.hpp index 306c87e4c..a532fda16 100644 --- a/InstructionSets/x86/DataPointerResolver.hpp +++ b/InstructionSets/x86/DataPointerResolver.hpp @@ -126,8 +126,11 @@ template constexpr Register register_for_source(Source source) /// * a register bank; and /// * a memory pool. /// -/// The register bank should implement `template DataT read()` and `template void write(DataT)`. -/// Those functions will be called only with registers and data types that are appropriate to the @c model. +/// The register bank should implement: +/// * `template DataT read()` and +/// * `template void write(DataT)`. +/// +/// Which will be called only with registers and data types that are appropriate to the @c model. /// /// The memory pool should implement `template DataT read(Source segment, uint32_t address)` and /// `template void write(Source segment, uint32_t address, DataT value)`. @@ -192,14 +195,14 @@ template void DataPointerResolver:: access(registers, memory, instruction, pointer, value); } -#define rw(v, r, is_write) \ - case Source::r: \ - using VType = typename std::remove_reference::type; \ - if constexpr (is_write) { \ +#define rw(v, r, is_write) \ + case Source::r: \ + using VType = typename std::remove_reference::type; \ + if constexpr (is_write) { \ registers.template write(Source::r)>(v); \ - } else { \ + } else { \ v = registers.template read(Source::r)>(); \ - } \ + } \ break; #define ALLREGS(v, i) rw(v, eAX, i); rw(v, eCX, i); \ From 059f30050016130877b6327e663b499bdd5e67f8 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 5 Oct 2023 14:37:58 -0400 Subject: [PATCH 005/128] Start fleshing out x86 performance. --- .../Implementation/PerformImplementation.hpp | 110 ++++++++++++++++++ InstructionSets/x86/Perform.hpp | 34 +++++- InstructionSets/x86/Status.hpp | 10 ++ .../Clock Signal.xcodeproj/project.pbxproj | 12 +- OSBindings/Mac/Clock SignalTests/8088Tests.mm | 23 ++++ 5 files changed, 186 insertions(+), 3 deletions(-) create mode 100644 InstructionSets/x86/Implementation/PerformImplementation.hpp diff --git a/InstructionSets/x86/Implementation/PerformImplementation.hpp b/InstructionSets/x86/Implementation/PerformImplementation.hpp new file mode 100644 index 000000000..34dedab8a --- /dev/null +++ b/InstructionSets/x86/Implementation/PerformImplementation.hpp @@ -0,0 +1,110 @@ +// +// PerformImplementation.hpp +// Clock Signal +// +// Created by Thomas Harte on 05/10/2023. +// Copyright © 2023 Thomas Harte. All rights reserved. +// + +#ifndef PerformImplementation_h +#define PerformImplementation_h + +namespace InstructionSet::x86 { + +namespace Primitive { + +void aaa(CPU::RegisterPair16 &ax, Status &status) { + /* + IF ((AL AND 0FH) > 9) OR (AF = 1) + THEN + AL ← (AL + 6); + AH ← AH + 1; + AF ← 1; + CF ← 1; + ELSE + AF ← 0; + CF ← 0; + FI; + AL ← AL AND 0FH; + */ + /* + The AF and CF flags are set to 1 if the adjustment results in a decimal carry; + otherwise they are cleared to 0. The OF, SF, ZF, and PF flags are undefined. + */ + if((ax.halves.low & 0x0f) > 9 || status.auxiliary_carry) { + ax.halves.low += 6; + ++ax.halves.high; + status.auxiliary_carry = status.carry = 1; + } else { + status.auxiliary_carry = status.carry = 0; + } +} + +void aad(CPU::RegisterPair16 &ax, uint8_t imm, Status &status) { + /* + tempAL ← AL; + tempAH ← AH; + AL ← (tempAL + (tempAH * imm8)) AND FFH; (* imm8 is set to 0AH for the AAD mnemonic *) + AH ← 0 + */ + /* + The SF, ZF, and PF flags are set according to the result; + the OF, AF, and CF flags are undefined. + */ + ax.halves.low = ax.halves.low + (ax.halves.high * imm); + ax.halves.high = 0; + status.sign = ax.halves.low & 0x80; + status.parity = status.zero = ax.halves.low; +} + +} + +template < + Model model, + Operation operation, + DataSize data_size, + typename FlowControllerT +> void perform( + CPU::RegisterPair16 &destination, + CPU::RegisterPair16 &source, + Status &status, + FlowControllerT &flow_controller +) { + switch(operation) { + case Operation::AAA: Primitive::aaa(destination, status); break; + case Operation::AAD: Primitive::aad(destination, source.halves.low, status); break; + } + + (void)flow_controller; +} + + +/*template < + Model model, + typename InstructionT, + typename FlowControllerT, + typename DataPointerResolverT, + typename RegistersT, + typename MemoryT, + typename IOT, + Operation operation +> void perform( + const InstructionT &instruction, + Status &status, + FlowControllerT &flow_controller, + DataPointerResolverT &resolver, + RegistersT ®isters, + MemoryT &memory, + IOT &io +) { + switch((operation != Operation::Invalid) ? operation : instruction.operation) { + default: + assert(false); + return; + } +}*/ + + +} + +#endif /* PerformImplementation_h */ diff --git a/InstructionSets/x86/Perform.hpp b/InstructionSets/x86/Perform.hpp index fe84da795..048894827 100644 --- a/InstructionSets/x86/Perform.hpp +++ b/InstructionSets/x86/Perform.hpp @@ -10,6 +10,9 @@ #define Perform_h #include "Instruction.hpp" +#include "Model.hpp" +#include "Status.hpp" +#include "../../Numeric/RegisterSizes.hpp" namespace InstructionSet::x86 { @@ -22,14 +25,15 @@ namespace InstructionSet::x86 { /// whatever is specifed in @c instruction. template < Model model, + typename InstructionT, typename FlowControllerT, typename DataPointerResolverT, typename RegistersT, typename MemoryT, typename IOT, - Operation operation = Operation::Undefined + Operation operation = Operation::Invalid > void perform( - const Instruction &instruction, + const InstructionT &instruction, Status &status, FlowControllerT &flow_controller, DataPointerResolverT &resolver, @@ -38,6 +42,32 @@ template < IOT &io ); +template < + Model model, + Operation operation, + DataSize data_size, + typename FlowControllerT +> void perform( + CPU::RegisterPair32 &destination, + CPU::RegisterPair32 &source, + Status &status, + FlowControllerT &flow_controller +); + +template < + Model model, + Operation operation, + DataSize data_size, + typename FlowControllerT +> void perform( + CPU::RegisterPair16 &destination, + CPU::RegisterPair16 &source, + Status &status, + FlowControllerT &flow_controller +); + } +#include "Implementation/PerformImplementation.hpp" + #endif /* Perform_h */ diff --git a/InstructionSets/x86/Status.hpp b/InstructionSets/x86/Status.hpp index 8ccb31c6b..4c922ca6b 100644 --- a/InstructionSets/x86/Status.hpp +++ b/InstructionSets/x86/Status.hpp @@ -54,6 +54,16 @@ static constexpr uint32_t VirtualMode = 1 << 17; } struct Status { + // Non-zero => set; zero => unset. + uint32_t carry; + uint32_t auxiliary_carry; + uint32_t sign; + + // Zero => set; non-zero => unset. + uint32_t zero; + + // Odd number of bits => set; even => unset. + uint32_t parity; }; } diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index a285bc85d..1f67f4bdb 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -1128,6 +1128,7 @@ 423BDC492AB24699008E37B6 /* 8088Tests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = 8088Tests.mm; sourceTree = ""; }; 42437B342ACF02A9006DFED1 /* Status.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Status.hpp; sourceTree = ""; }; 42437B352ACF0AA2006DFED1 /* Perform.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Perform.hpp; sourceTree = ""; }; + 42437B382ACF2798006DFED1 /* PerformImplementation.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = PerformImplementation.hpp; sourceTree = ""; }; 4281572E2AA0334300E16AA1 /* Carry.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Carry.hpp; sourceTree = ""; }; 428168372A16C25C008ECD27 /* LineLayout.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = LineLayout.hpp; sourceTree = ""; }; 428168392A37AFB4008ECD27 /* DispatcherTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = DispatcherTests.mm; sourceTree = ""; }; @@ -2325,6 +2326,14 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 42437B372ACF2798006DFED1 /* Implementation */ = { + isa = PBXGroup; + children = ( + 42437B382ACF2798006DFED1 /* PerformImplementation.hpp */, + ); + path = Implementation; + sourceTree = ""; + }; 42A5E8322ABBE16F00A0DD5D /* Neskell Tests */ = { isa = PBXGroup; children = ( @@ -4995,8 +5004,9 @@ 4BEDA3B825B25563000C2DBD /* Decoder.hpp */, 4BEDA3DB25B2588F000C2DBD /* Instruction.hpp */, 4BE3C69527CBC540000EAD28 /* Model.hpp */, - 42437B342ACF02A9006DFED1 /* Status.hpp */, 42437B352ACF0AA2006DFED1 /* Perform.hpp */, + 42437B342ACF02A9006DFED1 /* Status.hpp */, + 42437B372ACF2798006DFED1 /* Implementation */, ); path = x86; sourceTree = ""; diff --git a/OSBindings/Mac/Clock SignalTests/8088Tests.mm b/OSBindings/Mac/Clock SignalTests/8088Tests.mm index a7f81b22f..ce5fa332a 100644 --- a/OSBindings/Mac/Clock SignalTests/8088Tests.mm +++ b/OSBindings/Mac/Clock SignalTests/8088Tests.mm @@ -18,6 +18,7 @@ #include "NSData+dataWithContentsOfGZippedFile.h" #include "../../../InstructionSets/x86/Decoder.hpp" +#include "../../../InstructionSets/x86/Perform.hpp" namespace { @@ -185,4 +186,26 @@ constexpr char TestSuiteHome[] = "/Users/tharte/Projects/ProcessorTests/8088/v1" NSLog(@"%ld failures out of %ld tests: %@", failures.count, testFiles.count, [[failures allObjects] sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)]); } +- (void)testExecution { + CPU::RegisterPair16 source, dest; + InstructionSet::x86::Status status; + struct NoFlow { + } flow_controller; + + dest.full = 0xff; + source.full = 10; + + InstructionSet::x86::perform< + InstructionSet::x86::Model::i8086, + InstructionSet::x86::Operation::AAD, + InstructionSet::x86::DataSize::Byte + >( + dest, + source, + status, + flow_controller + ); + +} + @end From 09b2cfad8ad2c15677a9f096927439974e274578 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 5 Oct 2023 14:52:24 -0400 Subject: [PATCH 006/128] Add AAM and AAS. --- .../Implementation/PerformImplementation.hpp | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/InstructionSets/x86/Implementation/PerformImplementation.hpp b/InstructionSets/x86/Implementation/PerformImplementation.hpp index 34dedab8a..dbe68bc5d 100644 --- a/InstructionSets/x86/Implementation/PerformImplementation.hpp +++ b/InstructionSets/x86/Implementation/PerformImplementation.hpp @@ -57,6 +57,50 @@ void aad(CPU::RegisterPair16 &ax, uint8_t imm, Status &status) { status.parity = status.zero = ax.halves.low; } +void aam(CPU::RegisterPair16 &ax, uint8_t imm, Status &status) { + /* + tempAL ← AL; + AH ← tempAL / imm8; (* imm8 is set to 0AH for the AAD mnemonic *) + AL ← tempAL MOD imm8; + */ + /* + The SF, ZF, and PF flags are set according to the result. + The OF, AF, and CF flags are undefined. + */ + ax.halves.high = ax.halves.low / imm; + ax.halves.low = ax.halves.low % imm; + status.sign = ax.halves.low & 0x80; + status.parity = status.zero = ax.halves.low; +} + +void aas(CPU::RegisterPair16 &ax, Status &status) { + /* + IF ((AL AND 0FH) > 9) OR (AF = 1) + THEN + AL ← AL – 6; + AH ← AH – 1; + AF ← 1; + CF ← 1; + ELSE + CF ← 0; + AF ← 0; + FI; + AL ← AL AND 0FH; + */ + /* + The AF and CF flags are set to 1 if there is a decimal borrow; + otherwise, they are cleared to 0. The OF, SF, ZF, and PF flags are undefined. + */ + if((ax.halves.low & 0x0f) > 9 || status.auxiliary_carry) { + ax.halves.low -= 6; + --ax.halves.high; + status.auxiliary_carry = status.carry = 1; + } else { + status.auxiliary_carry = status.carry = 0; + } + ax.halves.low &= 0x0f; +} + } template < @@ -73,6 +117,8 @@ template < switch(operation) { case Operation::AAA: Primitive::aaa(destination, status); break; case Operation::AAD: Primitive::aad(destination, source.halves.low, status); break; + case Operation::AAM: Primitive::aam(destination, source.halves.low, status); break; + case Operation::AAS: Primitive::aas(destination, status); break; } (void)flow_controller; From 15acb1fc7ca4706b8f5c4e11ddb93d5b62041147 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 5 Oct 2023 15:49:07 -0400 Subject: [PATCH 007/128] Add ADC and ADD. --- .../Implementation/PerformImplementation.hpp | 92 +++++++++++++++++++ InstructionSets/x86/Status.hpp | 4 + OSBindings/Mac/Clock SignalTests/8088Tests.mm | 2 +- 3 files changed, 97 insertions(+), 1 deletion(-) diff --git a/InstructionSets/x86/Implementation/PerformImplementation.hpp b/InstructionSets/x86/Implementation/PerformImplementation.hpp index dbe68bc5d..8d20ab5b2 100644 --- a/InstructionSets/x86/Implementation/PerformImplementation.hpp +++ b/InstructionSets/x86/Implementation/PerformImplementation.hpp @@ -9,10 +9,51 @@ #ifndef PerformImplementation_h #define PerformImplementation_h +#include "../../../Numeric/Carry.hpp" + namespace InstructionSet::x86 { namespace Primitive { +// +// BEGIN TEMPORARY COPY AND PASTE SECTION. +// +// The following are largely excised from the M68k PerformImplementation.hpp; if there proves to be no +// reason further to specialise them, there'll be a factoring out. In some cases I've tightened the documentation. +// + +/// @returns An int of type @c IntT with only the most-significant bit set. +template constexpr IntT top_bit() { + static_assert(!std::numeric_limits::is_signed); + constexpr IntT max = std::numeric_limits::max(); + return max - (max >> 1); +} + +/// @returns The number of bits in @c IntT. +template constexpr int bit_size() { + return sizeof(IntT) * 8; +} + +/// @returns An int with the top bit indicating whether overflow occurred during the calculation of +/// • @c lhs + @c rhs (if @c is_add is true); or +/// • @c lhs - @c rhs (if @c is_add is false) +/// and the result was @c result. All other bits will be clear. +template +IntT overflow(IntT lhs, IntT rhs, IntT result) { + const IntT output_changed = result ^ rhs; + const IntT input_differed = lhs ^ rhs; + + if constexpr (is_add) { + return top_bit() & output_changed & ~input_differed; + } else { + return top_bit() & output_changed & input_differed; + } +} + +// +// END COPY AND PASTE SECTION. +// + void aaa(CPU::RegisterPair16 &ax, Status &status) { /* IF ((AL AND 0FH) > 9) OR (AF = 1) @@ -101,6 +142,42 @@ void aas(CPU::RegisterPair16 &ax, Status &status) { ax.halves.low &= 0x0f; } +template +void adc(IntT &destination, IntT source, Status &status) { + /* + DEST ← DEST + SRC + CF; + */ + /* + The OF, SF, ZF, AF, CF, and PF flags are set according to the result. + */ + const IntT result = destination + source + status.carry_bit(); + + status.carry = Numeric::carried_out() - 1>(destination, source, result); + status.auxiliary_carry = Numeric::carried_in<4>(destination, source, result); + status.sign = status.zero = status.parity = result; + status.overflow = overflow(destination, source, result); + + destination = result; +} + +template +void add(IntT &destination, IntT source, Status &status) { + /* + DEST ← DEST + SRC; + */ + /* + The OF, SF, ZF, AF, CF, and PF flags are set according to the result. + */ + const IntT result = destination + source; + + status.carry = Numeric::carried_out() - 1>(destination, source, result); + status.auxiliary_carry = Numeric::carried_in<4>(destination, source, result); + status.sign = status.zero = status.parity = result; + status.overflow = overflow(destination, source, result); + + destination = result; +} + } template < @@ -119,6 +196,21 @@ template < case Operation::AAD: Primitive::aad(destination, source.halves.low, status); break; case Operation::AAM: Primitive::aam(destination, source.halves.low, status); break; case Operation::AAS: Primitive::aas(destination, status); break; + + case Operation::ADC: + static_assert(operation != Operation::ADC || data_size == DataSize::Byte || data_size == DataSize::Word); + switch(data_size) { + case DataSize::Byte: Primitive::adc(destination.halves.low, source.halves.low, status); break; + case DataSize::Word: Primitive::adc(destination.full, source.full, status); break; + } + break; + case Operation::ADD: + static_assert(operation != Operation::ADD || data_size == DataSize::Byte || data_size == DataSize::Word); + switch(data_size) { + case DataSize::Byte: Primitive::add(destination.halves.low, source.halves.low, status); break; + case DataSize::Word: Primitive::add(destination.full, source.full, status); break; + } + break; } (void)flow_controller; diff --git a/InstructionSets/x86/Status.hpp b/InstructionSets/x86/Status.hpp index 4c922ca6b..dbb9b4295 100644 --- a/InstructionSets/x86/Status.hpp +++ b/InstructionSets/x86/Status.hpp @@ -58,12 +58,16 @@ struct Status { uint32_t carry; uint32_t auxiliary_carry; uint32_t sign; + uint32_t overflow; // Zero => set; non-zero => unset. uint32_t zero; // Odd number of bits => set; even => unset. uint32_t parity; + + // Convenience getters. + template IntT carry_bit() { return carry ? 1 : 0; } }; } diff --git a/OSBindings/Mac/Clock SignalTests/8088Tests.mm b/OSBindings/Mac/Clock SignalTests/8088Tests.mm index ce5fa332a..bf5577be3 100644 --- a/OSBindings/Mac/Clock SignalTests/8088Tests.mm +++ b/OSBindings/Mac/Clock SignalTests/8088Tests.mm @@ -197,7 +197,7 @@ constexpr char TestSuiteHome[] = "/Users/tharte/Projects/ProcessorTests/8088/v1" InstructionSet::x86::perform< InstructionSet::x86::Model::i8086, - InstructionSet::x86::Operation::AAD, + InstructionSet::x86::Operation::ADD, InstructionSet::x86::DataSize::Byte >( dest, From eb100e3b2907d24589c4890186bdf4d0a8a63245 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 5 Oct 2023 16:49:02 -0400 Subject: [PATCH 008/128] Start reforming; data size plus register aren't independent in finding a source. --- .../Implementation/PerformImplementation.hpp | 94 ++++++++++++------- InstructionSets/x86/Instruction.hpp | 4 + InstructionSets/x86/Perform.hpp | 14 +-- OSBindings/Mac/Clock SignalTests/8088Tests.mm | 38 ++++---- 4 files changed, 85 insertions(+), 65 deletions(-) diff --git a/InstructionSets/x86/Implementation/PerformImplementation.hpp b/InstructionSets/x86/Implementation/PerformImplementation.hpp index 8d20ab5b2..03cb5a1fe 100644 --- a/InstructionSets/x86/Implementation/PerformImplementation.hpp +++ b/InstructionSets/x86/Implementation/PerformImplementation.hpp @@ -182,65 +182,87 @@ void add(IntT &destination, IntT source, Status &status) { template < Model model, - Operation operation, DataSize data_size, - typename FlowControllerT + typename InstructionT, + typename FlowControllerT, + typename RegistersT, + typename MemoryT, + typename IOT > void perform( - CPU::RegisterPair16 &destination, - CPU::RegisterPair16 &source, + const InstructionT &instruction, Status &status, - FlowControllerT &flow_controller + [[maybe_unused]] FlowControllerT &flow_controller, + RegistersT ®isters, + [[maybe_unused]] MemoryT &memory, + [[maybe_unused]] IOT &io ) { - switch(operation) { - case Operation::AAA: Primitive::aaa(destination, status); break; - case Operation::AAD: Primitive::aad(destination, source.halves.low, status); break; - case Operation::AAM: Primitive::aam(destination, source.halves.low, status); break; - case Operation::AAS: Primitive::aas(destination, status); break; + using IntT = typename DataSizeType::type; - case Operation::ADC: - static_assert(operation != Operation::ADC || data_size == DataSize::Byte || data_size == DataSize::Word); - switch(data_size) { - case DataSize::Byte: Primitive::adc(destination.halves.low, source.halves.low, status); break; - case DataSize::Word: Primitive::adc(destination.full, source.full, status); break; - } - break; - case Operation::ADD: - static_assert(operation != Operation::ADD || data_size == DataSize::Byte || data_size == DataSize::Word); - switch(data_size) { - case DataSize::Byte: Primitive::add(destination.halves.low, source.halves.low, status); break; - case DataSize::Word: Primitive::add(destination.full, source.full, status); break; - } - break; + // Establish source() and destination() shorthand to fetch data if necessary. + IntT fetched_data; + bool needs_writeback = false; + auto data = [&](DataPointer source) -> IntT& { + // TODO. + return fetched_data; + }; + + auto source = [&]() -> IntT& { return data(instruction.source()); }; + auto destination = [&]() -> IntT& { return data(instruction.destination()); }; + + // Guide to the below: + // + // * use hard-coded register names where appropriate; + // * return directly if there is definitely no possible write back to RAM; + // * otherwise use the source() and destination() lambdas, and break in order to allow a writeback if necessary. + switch(instruction.operation) { + case Operation::AAA: Primitive::aaa(registers.ax(), status); return; + case Operation::AAD: Primitive::aad(registers.ax(), instruction.immediate(), status); return; + case Operation::AAM: Primitive::aam(registers.ax(), instruction.immediate(), status); return; + case Operation::AAS: Primitive::aas(registers.ax(), status); return; + + case Operation::ADC: Primitive::adc(destination(), source(), status); break; + case Operation::ADD: Primitive::add(destination(), source(), status); break; } - (void)flow_controller; + // Write to memory if required to complete this operation. + if(needs_writeback) { + // TODO. + } } - -/*template < +template < Model model, typename InstructionT, typename FlowControllerT, - typename DataPointerResolverT, typename RegistersT, typename MemoryT, - typename IOT, - Operation operation + typename IOT > void perform( const InstructionT &instruction, Status &status, FlowControllerT &flow_controller, - DataPointerResolverT &resolver, RegistersT ®isters, MemoryT &memory, IOT &io ) { - switch((operation != Operation::Invalid) ? operation : instruction.operation) { - default: - assert(false); - return; + // Dispatch to a function just like this that is specialised on data size. + // Fetching will occur in that specialised function, per the overlapping + // meaning of register names. + switch(instruction.operation_size()) { + case DataSize::Byte: + perform(instruction, status, flow_controller, registers, memory, io); + break; + case DataSize::Word: + perform(instruction, status, flow_controller, registers, memory, io); + break; + case DataSize::DWord: + perform(instruction, status, flow_controller, registers, memory, io); + break; + case DataSize::None: + perform(instruction, status, flow_controller, registers, memory, io); + break; } -}*/ +} } diff --git a/InstructionSets/x86/Instruction.hpp b/InstructionSets/x86/Instruction.hpp index d5b78d9b9..a22796244 100644 --- a/InstructionSets/x86/Instruction.hpp +++ b/InstructionSets/x86/Instruction.hpp @@ -370,6 +370,10 @@ enum class DataSize: uint8_t { None = 3, }; +template struct DataSizeType { using type = uint8_t; }; +template <> struct DataSizeType { using type = uint16_t; }; +template <> struct DataSizeType { using type = uint32_t; }; + constexpr int byte_size(DataSize size) { return (1 << int(size)) & 7; } diff --git a/InstructionSets/x86/Perform.hpp b/InstructionSets/x86/Perform.hpp index 048894827..387ccaea5 100644 --- a/InstructionSets/x86/Perform.hpp +++ b/InstructionSets/x86/Perform.hpp @@ -16,33 +16,27 @@ namespace InstructionSet::x86 { -/// Performs @c instruction using @c resolver to obtain to query @c registers and/or @c memory as required, using @c io for port input/output, +/// 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. -/// -/// If the template parameter @c operation is not @c Operation::Undefined then that operation will be performed, ignoring -/// whatever is specifed in @c instruction. template < Model model, typename InstructionT, typename FlowControllerT, - typename DataPointerResolverT, typename RegistersT, typename MemoryT, - typename IOT, - Operation operation = Operation::Invalid + typename IOT > void perform( const InstructionT &instruction, Status &status, FlowControllerT &flow_controller, - DataPointerResolverT &resolver, RegistersT ®isters, MemoryT &memory, IOT &io ); -template < +/*template < Model model, Operation operation, DataSize data_size, @@ -64,7 +58,7 @@ template < CPU::RegisterPair16 &source, Status &status, FlowControllerT &flow_controller -); +);*/ } diff --git a/OSBindings/Mac/Clock SignalTests/8088Tests.mm b/OSBindings/Mac/Clock SignalTests/8088Tests.mm index bf5577be3..ea53bf776 100644 --- a/OSBindings/Mac/Clock SignalTests/8088Tests.mm +++ b/OSBindings/Mac/Clock SignalTests/8088Tests.mm @@ -187,25 +187,25 @@ constexpr char TestSuiteHome[] = "/Users/tharte/Projects/ProcessorTests/8088/v1" } - (void)testExecution { - CPU::RegisterPair16 source, dest; - InstructionSet::x86::Status status; - struct NoFlow { - } flow_controller; - - dest.full = 0xff; - source.full = 10; - - InstructionSet::x86::perform< - InstructionSet::x86::Model::i8086, - InstructionSet::x86::Operation::ADD, - InstructionSet::x86::DataSize::Byte - >( - dest, - source, - status, - flow_controller - ); - +// CPU::RegisterPair16 source, dest; +// InstructionSet::x86::Status status; +// struct NoFlow { +// } flow_controller; +// +// dest.full = 0xff; +// source.full = 10; +// +// InstructionSet::x86::perform< +// InstructionSet::x86::Model::i8086, +// InstructionSet::x86::Operation::ADD, +// InstructionSet::x86::DataSize::Byte +// >( +// dest, +// source, +// status, +// flow_controller +// ); +// } @end From ada411c0d8bb940956d550f5e7fdf3ad4ef6c6f2 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 5 Oct 2023 17:06:00 -0400 Subject: [PATCH 009/128] It's differing mildly from DataPointResolver, but segue towards a world of real data. --- .../Implementation/PerformImplementation.hpp | 88 ++++++++++++++++++- InstructionSets/x86/Model.hpp | 5 ++ 2 files changed, 89 insertions(+), 4 deletions(-) diff --git a/InstructionSets/x86/Implementation/PerformImplementation.hpp b/InstructionSets/x86/Implementation/PerformImplementation.hpp index 03cb5a1fe..4f13b707d 100644 --- a/InstructionSets/x86/Implementation/PerformImplementation.hpp +++ b/InstructionSets/x86/Implementation/PerformImplementation.hpp @@ -197,12 +197,92 @@ template < [[maybe_unused]] IOT &io ) { using IntT = typename DataSizeType::type; + using AddressT = typename AddressT::type; // Establish source() and destination() shorthand to fetch data if necessary. - IntT fetched_data; - bool needs_writeback = false; + IntT fetched_data = 0, original_data = 0; + Source segment; + AddressT address; + + static constexpr IntT zero = 0; auto data = [&](DataPointer source) -> IntT& { - // TODO. + // Rules: + // + // * if this is a memory access, set target_address and break; + // * otherwise return the appropriate value. + switch(source.source()) { + case Source::eAX: + switch(data_size) { + default: return registers.al(); + case DataSize::Word: return registers.ax(); + case DataSize::DWord: return registers.eax(); + } + case Source::eCX: + switch(data_size) { + default: return registers.cl(); + case DataSize::Word: return registers.cx(); + case DataSize::DWord: return registers.ecx(); + } + case Source::eDX: + switch(data_size) { + default: return registers.dl(); + case DataSize::Word: return registers.dx(); + case DataSize::DWord: return registers.edx(); + } + case Source::eBX: + switch(data_size) { + default: return registers.bl(); + case DataSize::Word: return registers.bx(); + case DataSize::DWord: return registers.ebx(); + } + case Source::eSPorAH: + switch(data_size) { + default: return registers.ah(); + case DataSize::Word: return registers.sp(); + case DataSize::DWord: return registers.esp(); + } + case Source::eBPorCH: + switch(data_size) { + default: return registers.ch(); + case DataSize::Word: return registers.bp(); + case DataSize::DWord: return registers.ebp(); + } + case Source::eSIorDH: + switch(data_size) { + default: return registers.dh(); + case DataSize::Word: return registers.si(); + case DataSize::DWord: return registers.esi(); + } + case Source::eDIorBH: + switch(data_size) { + default: return registers.bh(); + case DataSize::Word: return registers.di(); + case DataSize::DWord: return registers.edi(); + } + + case Source::ES: return registers.es(); + case Source::CS: return registers.cs(); + case Source::SS: return registers.ss(); + case Source::DS: return registers.ds(); + case Source::FS: return registers.fs(); + case Source::GS: return registers.gs(); + + case Source::Immediate: // TODO (here the use of a reference falls down?) + + case Source::None: return zero; + + case Source::Indirect: // TODO + case Source::IndirectNoBase: // TODO + + case Source::DirectAddress: + address = instruction.offset(); + break; + } + + // If execution has reached here then a memory fetch is required. + // Do it and exit. + segment = Source::DS; // TODO. + fetched_data = original_data = memory.template read(segment, address); return fetched_data; }; @@ -225,7 +305,7 @@ template < } // Write to memory if required to complete this operation. - if(needs_writeback) { + if(original_data != fetched_data) { // TODO. } } diff --git a/InstructionSets/x86/Model.hpp b/InstructionSets/x86/Model.hpp index 4a4ac0bdb..e497cd5a6 100644 --- a/InstructionSets/x86/Model.hpp +++ b/InstructionSets/x86/Model.hpp @@ -9,6 +9,8 @@ #ifndef Model_h #define Model_h +#include + namespace InstructionSet::x86 { enum class Model { @@ -20,6 +22,9 @@ enum class Model { static constexpr bool is_32bit(Model model) { return model >= Model::i80386; } +template struct AddressT { using type = uint16_t; }; +template <> struct AddressT { using type = uint32_t; }; + } #endif /* Model_h */ From f411a961a3608a09c9f0c29f179db154f6438c32 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 5 Oct 2023 17:12:38 -0400 Subject: [PATCH 010/128] Create a central location for avoiding segment conditionality. --- InstructionSets/x86/DataPointerResolver.hpp | 8 ++++---- .../x86/Implementation/PerformImplementation.hpp | 2 +- InstructionSets/x86/Instruction.cpp | 2 +- InstructionSets/x86/Instruction.hpp | 9 ++++++++- OSBindings/Mac/Clock SignalTests/x86DecoderTests.mm | 6 +++--- 5 files changed, 17 insertions(+), 10 deletions(-) diff --git a/InstructionSets/x86/DataPointerResolver.hpp b/InstructionSets/x86/DataPointerResolver.hpp index a532fda16..028984e37 100644 --- a/InstructionSets/x86/DataPointerResolver.hpp +++ b/InstructionSets/x86/DataPointerResolver.hpp @@ -277,9 +277,9 @@ template void DataPointerResolver(instruction.data_segment(), instruction.displacement()); + value = memory.template read(instruction.segment_override(), instruction.displacement()); } break; case Source::Immediate: @@ -295,13 +295,13 @@ template void DataPointerResolver( \ - instruction.data_segment(), \ + instruction.segment_override(), \ address \ ); \ } \ diff --git a/InstructionSets/x86/Implementation/PerformImplementation.hpp b/InstructionSets/x86/Implementation/PerformImplementation.hpp index 4f13b707d..15243c06f 100644 --- a/InstructionSets/x86/Implementation/PerformImplementation.hpp +++ b/InstructionSets/x86/Implementation/PerformImplementation.hpp @@ -281,7 +281,7 @@ template < // If execution has reached here then a memory fetch is required. // Do it and exit. - segment = Source::DS; // TODO. + segment = source.segment(instruction.segment_override()); fetched_data = original_data = memory.template read(segment, address); return fetched_data; }; diff --git a/InstructionSets/x86/Instruction.cpp b/InstructionSets/x86/Instruction.cpp index c247a4f11..fd0a9b8d5 100644 --- a/InstructionSets/x86/Instruction.cpp +++ b/InstructionSets/x86/Instruction.cpp @@ -383,7 +383,7 @@ std::string InstructionSet::x86::to_string( stream << InstructionSet::x86::to_string(operation_size) << ' '; } - Source segment = instruction.data_segment(); + Source segment = instruction.segment_override(); if(segment == Source::None) { segment = pointer.default_segment(); if(segment == Source::None) { diff --git a/InstructionSets/x86/Instruction.hpp b/InstructionSets/x86/Instruction.hpp index a22796244..3bc2b14a4 100644 --- a/InstructionSets/x86/Instruction.hpp +++ b/InstructionSets/x86/Instruction.hpp @@ -625,6 +625,13 @@ class DataPointer { } } + constexpr Source segment(Source segment_override) const { + // TODO: remove conditionaluty here. + if(segment_override != Source::None) return segment_override; + if(const auto segment = default_segment(); segment != Source::None) return segment; + return Source::DS; + } + template constexpr Source base() const { if constexpr (obscure_indirectNoBase) { return (source_ <= Source::IndirectNoBase) ? Source::None : sib_.base(); @@ -766,7 +773,7 @@ template class Instruction { /// On x86 a segment override cannot modify the segment used as a destination in string instructions, /// or that used by stack instructions, but this function does not spend the time necessary to provide /// the correct default for those. - Source data_segment() const { + Source segment_override() const { if(!has_length_extension()) return Source::None; return Source( int(Source::ES) + diff --git a/OSBindings/Mac/Clock SignalTests/x86DecoderTests.mm b/OSBindings/Mac/Clock SignalTests/x86DecoderTests.mm index 7544cf22a..26bf5b2d2 100644 --- a/OSBindings/Mac/Clock SignalTests/x86DecoderTests.mm +++ b/OSBindings/Mac/Clock SignalTests/x86DecoderTests.mm @@ -411,7 +411,7 @@ decode(const std::initializer_list &stream, bool set_32_bit = false) { // add DWORD PTR [edi-0x42],0x9f683aa9 // lock jp 0xfffffff0 (from 0000000e) test(instructions[0], DataSize::DWord, Operation::INC, Source::eDX); - XCTAssertEqual(instructions[0].data_segment(), Source::CS); + XCTAssertEqual(instructions[0].segment_override(), Source::CS); test(instructions[1], DataSize::Byte, Operation::OR, Source::Immediate, Source::eAX, 0x9); test(instructions[2], DataSize::DWord, Operation::ADD, Source::Immediate, ScaleIndexBase(Source::eDI), 0x9f683aa9, -0x42); test(instructions[3], Operation::JP, 0, -30); @@ -422,7 +422,7 @@ decode(const std::initializer_list &stream, bool set_32_bit = false) { // stos BYTE PTR es:[edi],al // pusha test(instructions[4], DataSize::Byte, Operation::MOV, Source::Immediate, Source::AH, 0xc1); - XCTAssertEqual(instructions[4].data_segment(), Source::DS); + XCTAssertEqual(instructions[4].segment_override(), Source::DS); test(instructions[5], DataSize::Word, Operation::POP, Source::None, Source::DS); test(instructions[6], DataSize::Byte, Operation::STOS); test(instructions[7], Operation::PUSHA); @@ -465,7 +465,7 @@ decode(const std::initializer_list &stream, bool set_32_bit = false) { test(instructions[21], DataSize::Byte, Operation::XOR, Source::Immediate, Source::eAX, 0x45); test(instructions[22], DataSize::DWord, Operation::LDS, ScaleIndexBase(Source::eCX), Source::eDX); test(instructions[23], DataSize::Byte, Operation::MOV, Source::eAX, Source::DirectAddress, 0xe4dba6d3); - XCTAssertEqual(instructions[23].data_segment(), Source::DS); + XCTAssertEqual(instructions[23].segment_override(), Source::DS); // pop ds // movs DWORD PTR es:[edi],DWORD PTR ds:[esi] From 6d392852d2b9e0cb2005d828d2c5f54515f6a983 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 5 Oct 2023 22:27:52 -0400 Subject: [PATCH 011/128] Hack on through to something that builds. --- .../Implementation/PerformImplementation.hpp | 116 ++++++------ InstructionSets/x86/Perform.hpp | 1 - OSBindings/Mac/Clock SignalTests/8088Tests.mm | 174 +++++++++++++----- 3 files changed, 187 insertions(+), 104 deletions(-) diff --git a/InstructionSets/x86/Implementation/PerformImplementation.hpp b/InstructionSets/x86/Implementation/PerformImplementation.hpp index 15243c06f..2ac3cfabe 100644 --- a/InstructionSets/x86/Implementation/PerformImplementation.hpp +++ b/InstructionSets/x86/Implementation/PerformImplementation.hpp @@ -10,6 +10,7 @@ #define PerformImplementation_h #include "../../../Numeric/Carry.hpp" +#include "../../../Numeric/RegisterSizes.hpp" namespace InstructionSet::x86 { @@ -79,6 +80,7 @@ void aaa(CPU::RegisterPair16 &ax, Status &status) { } else { status.auxiliary_carry = status.carry = 0; } + ax.halves.low &= 0x0f; } void aad(CPU::RegisterPair16 &ax, uint8_t imm, Status &status) { @@ -199,73 +201,63 @@ template < using IntT = typename DataSizeType::type; using AddressT = typename AddressT::type; - // Establish source() and destination() shorthand to fetch data if necessary. - IntT fetched_data = 0, original_data = 0; - Source segment; - AddressT address; - - static constexpr IntT zero = 0; + IntT zero = 0; auto data = [&](DataPointer source) -> IntT& { // Rules: // // * if this is a memory access, set target_address and break; // * otherwise return the appropriate value. + AddressT address; switch(source.source()) { case Source::eAX: - switch(data_size) { - default: return registers.al(); - case DataSize::Word: return registers.ax(); - case DataSize::DWord: return registers.eax(); - } + if constexpr (is_32bit(model) && data_size == DataSize::DWord) { return registers.eax(); } + else if constexpr (data_size == DataSize::DWord) { return zero; } + else if constexpr (data_size == DataSize::Word) { return registers.ax(); } + else { return registers.al(); } case Source::eCX: - switch(data_size) { - default: return registers.cl(); - case DataSize::Word: return registers.cx(); - case DataSize::DWord: return registers.ecx(); - } + if constexpr (is_32bit(model) && data_size == DataSize::DWord) { return registers.ecx(); } + else if constexpr (data_size == DataSize::DWord) { return zero; } + else if constexpr (data_size == DataSize::Word) { return registers.cx(); } + else { return registers.cl(); } case Source::eDX: - switch(data_size) { - default: return registers.dl(); - case DataSize::Word: return registers.dx(); - case DataSize::DWord: return registers.edx(); - } + if constexpr (is_32bit(model) && data_size == DataSize::DWord) { return registers.edx(); } + else if constexpr (data_size == DataSize::DWord) { return zero; } + else if constexpr (data_size == DataSize::Word) { return registers.dx(); } + else { return registers.dl(); } case Source::eBX: - switch(data_size) { - default: return registers.bl(); - case DataSize::Word: return registers.bx(); - case DataSize::DWord: return registers.ebx(); - } + if constexpr (is_32bit(model) && data_size == DataSize::DWord) { return registers.ebx(); } + else if constexpr (data_size == DataSize::DWord) { return zero; } + else if constexpr (data_size == DataSize::Word) { return registers.bx(); } + else { return registers.bl(); } case Source::eSPorAH: - switch(data_size) { - default: return registers.ah(); - case DataSize::Word: return registers.sp(); - case DataSize::DWord: return registers.esp(); - } + if constexpr (is_32bit(model) && data_size == DataSize::DWord) { return registers.esp(); } + else if constexpr (data_size == DataSize::DWord) { return zero; } + else if constexpr (data_size == DataSize::Word) { return registers.sp(); } + else { return registers.ah(); } case Source::eBPorCH: - switch(data_size) { - default: return registers.ch(); - case DataSize::Word: return registers.bp(); - case DataSize::DWord: return registers.ebp(); - } + if constexpr (is_32bit(model) && data_size == DataSize::DWord) { return registers.ebp(); } + else if constexpr (data_size == DataSize::DWord) { return zero; } + else if constexpr (data_size == DataSize::Word) { return registers.bp(); } + else { return registers.ch(); } case Source::eSIorDH: - switch(data_size) { - default: return registers.dh(); - case DataSize::Word: return registers.si(); - case DataSize::DWord: return registers.esi(); - } + if constexpr (is_32bit(model) && data_size == DataSize::DWord) { return registers.esi(); } + else if constexpr (data_size == DataSize::DWord) { return zero; } + else if constexpr (data_size == DataSize::Word) { return registers.si(); } + else { return registers.dh(); } case Source::eDIorBH: - switch(data_size) { - default: return registers.bh(); - case DataSize::Word: return registers.di(); - case DataSize::DWord: return registers.edi(); - } + if constexpr (is_32bit(model) && data_size == DataSize::DWord) { return registers.edi(); } + else if constexpr (data_size == DataSize::DWord) { return zero; } + else if constexpr (data_size == DataSize::Word) { return registers.di(); } + else { return registers.bh(); } - case Source::ES: return registers.es(); - case Source::CS: return registers.cs(); - case Source::SS: return registers.ss(); - case Source::DS: return registers.ds(); - case Source::FS: return registers.fs(); - case Source::GS: return registers.gs(); + // TODO: the below. + default: +// case Source::ES: return registers.es(); +// case Source::CS: return registers.cs(); +// case Source::SS: return registers.ss(); +// case Source::DS: return registers.ds(); +// case Source::FS: return registers.fs(); +// case Source::GS: return registers.gs(); case Source::Immediate: // TODO (here the use of a reference falls down?) @@ -281,11 +273,11 @@ template < // If execution has reached here then a memory fetch is required. // Do it and exit. - segment = source.segment(instruction.segment_override()); - fetched_data = original_data = memory.template read(segment, address); - return fetched_data; + const Source segment = source.segment(instruction.segment_override()); + return memory.template access(segment, address); }; + // Establish source() and destination() shorthand to fetch data if necessary. auto source = [&]() -> IntT& { return data(instruction.source()); }; auto destination = [&]() -> IntT& { return data(instruction.destination()); }; @@ -295,19 +287,21 @@ template < // * return directly if there is definitely no possible write back to RAM; // * otherwise use the source() and destination() lambdas, and break in order to allow a writeback if necessary. switch(instruction.operation) { - case Operation::AAA: Primitive::aaa(registers.ax(), status); return; - case Operation::AAD: Primitive::aad(registers.ax(), instruction.immediate(), status); return; - case Operation::AAM: Primitive::aam(registers.ax(), instruction.immediate(), status); return; - case Operation::AAS: Primitive::aas(registers.ax(), status); return; + 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); return; + case Operation::AAS: Primitive::aas(registers.axp(), status); return; case Operation::ADC: Primitive::adc(destination(), source(), status); break; case Operation::ADD: Primitive::add(destination(), source(), status); break; } // Write to memory if required to complete this operation. - if(original_data != fetched_data) { +// if(original_data != fetched_data) { // TODO. - } +// } } template < diff --git a/InstructionSets/x86/Perform.hpp b/InstructionSets/x86/Perform.hpp index 387ccaea5..da77b89b0 100644 --- a/InstructionSets/x86/Perform.hpp +++ b/InstructionSets/x86/Perform.hpp @@ -12,7 +12,6 @@ #include "Instruction.hpp" #include "Model.hpp" #include "Status.hpp" -#include "../../Numeric/RegisterSizes.hpp" namespace InstructionSet::x86 { diff --git a/OSBindings/Mac/Clock SignalTests/8088Tests.mm b/OSBindings/Mac/Clock SignalTests/8088Tests.mm index ea53bf776..8408895dd 100644 --- a/OSBindings/Mac/Clock SignalTests/8088Tests.mm +++ b/OSBindings/Mac/Clock SignalTests/8088Tests.mm @@ -19,6 +19,7 @@ #include "../../../InstructionSets/x86/Decoder.hpp" #include "../../../InstructionSets/x86/Perform.hpp" +#include "../../../Numeric/RegisterSizes.hpp" namespace { @@ -59,22 +60,30 @@ constexpr char TestSuiteHome[] = "/Users/tharte/Projects/ProcessorTests/8088/v1" return [fullPaths sortedArrayUsingSelector:@selector(compare:)]; } +- (NSArray *)testsInFile:(NSString *)file { + NSData *data = [NSData dataWithContentsOfGZippedFile:file]; + return [NSJSONSerialization JSONObjectWithData:data options:0 error:nil]; +} + - (NSString *)toString:(const InstructionSet::x86::Instruction &)instruction offsetLength:(int)offsetLength immediateLength:(int)immediateLength { const auto operation = to_string(instruction, InstructionSet::x86::Model::i8086, offsetLength, immediateLength); return [[NSString stringWithUTF8String:operation.c_str()] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; } -- (bool)applyDecodingTest:(NSDictionary *)test file:(NSString *)file assert:(BOOL)assert { - using Decoder = InstructionSet::x86::Decoder; - Decoder decoder; - - // Build a vector of the instruction bytes; this makes manual step debugging easier. - NSArray *encoding = test[@"bytes"]; +- (std::vector)bytes:(NSArray *)encoding { std::vector data; data.reserve(encoding.count); for(NSNumber *number in encoding) { data.push_back([number intValue]); } + return data; +} + +- (bool)applyDecodingTest:(NSDictionary *)test file:(NSString *)file assert:(BOOL)assert { + InstructionSet::x86::Decoder decoder; + + // Build a vector of the instruction bytes; this makes manual step debugging easier. + const auto data = [self bytes:test[@"bytes"]]; auto hex_instruction = [&]() -> NSString * { NSMutableString *hexInstruction = [[NSMutableString alloc] init]; for(uint8_t byte: data) { @@ -84,18 +93,19 @@ constexpr char TestSuiteHome[] = "/Users/tharte/Projects/ProcessorTests/8088/v1" }; const auto decoded = decoder.decode(data.data(), data.size()); + const bool sizeMatched = decoded.first == data.size(); if(assert) { XCTAssert( - decoded.first == [encoding count], + sizeMatched, "Wrong length of instruction decoded for %@ — decoded %d rather than %lu from %@; file %@", test[@"name"], decoded.first, - (unsigned long)[encoding count], + (unsigned long)data.size(), hex_instruction(), file ); } - if(decoded.first != [encoding count]) { + if(!sizeMatched) { return false; } @@ -158,54 +168,134 @@ constexpr char TestSuiteHome[] = "/Users/tharte/Projects/ProcessorTests/8088/v1" return isEqual; } -- (void)testDecoding { - NSMutableSet *failures = [[NSMutableSet alloc] init]; - NSArray *testFiles = [self testFiles]; +- (bool)applyExecutionTest:(NSDictionary *)test file:(NSString *)file assert:(BOOL)assert { + InstructionSet::x86::Decoder decoder; + const auto data = [self bytes:test[@"bytes"]]; + const auto decoded = decoder.decode(data.data(), data.size()); - for(NSString *file in testFiles) { - NSData *data = [NSData dataWithContentsOfGZippedFile:file]; - NSArray *testsInFile = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil]; - NSUInteger successes = 0; - for(NSDictionary *test in testsInFile) { + struct Registers { + CPU::RegisterPair16 ax_; + uint8_t &al() { return ax_.halves.low; } + uint8_t &ah() { return ax_.halves.high; } + uint16_t &ax() { return ax_.full; } + + CPU::RegisterPair16 &axp() { return ax_; } + + CPU::RegisterPair16 cx_; + uint8_t &cl() { return cx_.halves.low; } + uint8_t &ch() { return cx_.halves.high; } + uint16_t &cx() { return cx_.full; } + + CPU::RegisterPair16 dx_; + uint8_t &dl() { return dx_.halves.low; } + uint8_t &dh() { return dx_.halves.high; } + uint16_t &dx() { return dx_.full; } + + CPU::RegisterPair16 bx_; + uint8_t &bl() { return bx_.halves.low; } + uint8_t &bh() { return bx_.halves.high; } + uint16_t &bx() { return bx_.full; } + + uint16_t sp_; + uint16_t &sp() { return sp_; } + + uint16_t bp_; + uint16_t &bp() { return bp_; } + + uint16_t si_; + uint16_t &si() { return si_; } + + uint16_t di_; + uint16_t &di() { return di_; } + + uint16_t es_, cs_, ds_, ss_; + }; + struct Memory { + std::vector memory; + const Registers ®isters_; + + Memory(Registers ®isters) : registers_(registers) { + memory.resize(1024*1024); + } + + template IntT &access([[maybe_unused]] InstructionSet::x86::Source segment, uint16_t address) { + uint32_t physical_address; + using Source = InstructionSet::x86::Source; + switch(segment) { + default: address = registers_.ds_; break; + case Source::ES: address = registers_.es_; break; + case Source::CS: address = registers_.cs_; break; + case Source::DS: address = registers_.ds_; break; + } + physical_address = ((physical_address << 4) + address) & 0xf'ffff; + return *reinterpret_cast(&memory[physical_address]); + } + }; + struct IO { + }; + struct FlowController { + }; + + InstructionSet::x86::Status status; + FlowController flow_controller; + Registers registers; + Memory memory(registers); + IO io; + + InstructionSet::x86::perform( + decoded.second, + status, + flow_controller, + registers, + memory, + io + ); + + return false; +} + +- (void)printFailures:(NSArray *)failures { + NSLog( + @"%ld failures out of %ld tests: %@", + failures.count, + [self testFiles].count, + [failures sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)]); +} + +- (void)testDecoding { + NSMutableArray *failures = [[NSMutableArray alloc] init]; + for(NSString *file in [self testFiles]) { + for(NSDictionary *test in [self testsInFile:file]) { // A single failure per instruction is fine. if(![self applyDecodingTest:test file:file assert:YES]) { [failures addObject:file]; // Attempt a second decoding, to provide a debugger hook. [self applyDecodingTest:test file:file assert:NO]; - break; } - ++successes; - } - if(successes != [testsInFile count]) { - NSLog(@"Failed after %ld successes", successes); } } - NSLog(@"%ld failures out of %ld tests: %@", failures.count, testFiles.count, [[failures allObjects] sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)]); + [self printFailures:failures]; } - (void)testExecution { -// CPU::RegisterPair16 source, dest; -// InstructionSet::x86::Status status; -// struct NoFlow { -// } flow_controller; -// -// dest.full = 0xff; -// source.full = 10; -// -// InstructionSet::x86::perform< -// InstructionSet::x86::Model::i8086, -// InstructionSet::x86::Operation::ADD, -// InstructionSet::x86::DataSize::Byte -// >( -// dest, -// source, -// status, -// flow_controller -// ); -// + NSMutableArray *failures = [[NSMutableArray alloc] init]; + for(NSString *file in [self testFiles]) { + for(NSDictionary *test in [self testsInFile:file]) { + // A single failure per instruction is fine. + if(![self applyExecutionTest:test file:file assert:YES]) { + [failures addObject:file]; + + // Attempt a second decoding, to provide a debugger hook. + [self applyExecutionTest:test file:file assert:NO]; + break; + } + } + } + + [self printFailures:failures]; } @end From 90a8999b4b1e9a37bf5fa4dce12bc1d43fb52235 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 5 Oct 2023 22:29:15 -0400 Subject: [PATCH 012/128] Fix typo. --- 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 3bc2b14a4..ca7a37269 100644 --- a/InstructionSets/x86/Instruction.hpp +++ b/InstructionSets/x86/Instruction.hpp @@ -626,7 +626,7 @@ class DataPointer { } constexpr Source segment(Source segment_override) const { - // TODO: remove conditionaluty here. + // TODO: remove conditionality here. if(segment_override != Source::None) return segment_override; if(const auto segment = default_segment(); segment != Source::None) return segment; return Source::DS; From 28c7d27cac98671ea54de235e00aec21e9643432 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 6 Oct 2023 11:07:33 -0400 Subject: [PATCH 013/128] Establish some proportion of state, ready to execute _something_. --- .../Implementation/PerformImplementation.hpp | 3 +- InstructionSets/x86/Status.hpp | 6 ++++ OSBindings/Mac/Clock SignalTests/8088Tests.mm | 32 ++++++++++++++++--- 3 files changed, 36 insertions(+), 5 deletions(-) diff --git a/InstructionSets/x86/Implementation/PerformImplementation.hpp b/InstructionSets/x86/Implementation/PerformImplementation.hpp index 2ac3cfabe..eb879db40 100644 --- a/InstructionSets/x86/Implementation/PerformImplementation.hpp +++ b/InstructionSets/x86/Implementation/PerformImplementation.hpp @@ -287,7 +287,8 @@ template < // * return directly if there is definitely no possible write back to RAM; // * otherwise use the source() and destination() lambdas, and break in order to allow a writeback if necessary. switch(instruction.operation) { - default: assert(false); + default: return; + //assert(false); case Operation::AAA: Primitive::aaa(registers.axp(), status); return; case Operation::AAD: Primitive::aad(registers.axp(), instruction.operand(), status); return; diff --git a/InstructionSets/x86/Status.hpp b/InstructionSets/x86/Status.hpp index dbb9b4295..b1c4b2e1e 100644 --- a/InstructionSets/x86/Status.hpp +++ b/InstructionSets/x86/Status.hpp @@ -68,6 +68,12 @@ struct Status { // Convenience getters. template IntT carry_bit() { return carry ? 1 : 0; } + + void set(uint16_t value) { + carry = value & ConditionCode::Carry; + + // TODO: the rest. + } }; } diff --git a/OSBindings/Mac/Clock SignalTests/8088Tests.mm b/OSBindings/Mac/Clock SignalTests/8088Tests.mm index 8408895dd..7c5f53033 100644 --- a/OSBindings/Mac/Clock SignalTests/8088Tests.mm +++ b/OSBindings/Mac/Clock SignalTests/8088Tests.mm @@ -209,6 +209,7 @@ constexpr char TestSuiteHome[] = "/Users/tharte/Projects/ProcessorTests/8088/v1" uint16_t &di() { return di_; } uint16_t es_, cs_, ds_, ss_; + uint16_t ip_; }; struct Memory { std::vector memory; @@ -222,10 +223,10 @@ constexpr char TestSuiteHome[] = "/Users/tharte/Projects/ProcessorTests/8088/v1" uint32_t physical_address; using Source = InstructionSet::x86::Source; switch(segment) { - default: address = registers_.ds_; break; - case Source::ES: address = registers_.es_; break; - case Source::CS: address = registers_.cs_; break; - case Source::DS: address = registers_.ds_; break; + default: physical_address = registers_.ds_; break; + case Source::ES: physical_address = registers_.es_; break; + case Source::CS: physical_address = registers_.cs_; break; + case Source::DS: physical_address = registers_.ds_; break; } physical_address = ((physical_address << 4) + address) & 0xf'ffff; return *reinterpret_cast(&memory[physical_address]); @@ -242,6 +243,29 @@ constexpr char TestSuiteHome[] = "/Users/tharte/Projects/ProcessorTests/8088/v1" Memory memory(registers); IO io; + // Apply initial state. + NSDictionary *const initial = test[@"initial"]; + for(NSArray *ram in initial[@"ram"]) { + memory.memory[[ram[0] intValue]] = [ram[1] intValue]; + } + NSDictionary *const initial_registers = initial[@"regs"]; + registers.ax_.full = [initial_registers[@"ax"] intValue]; + registers.bx_.full = [initial_registers[@"bx"] intValue]; + registers.cx_.full = [initial_registers[@"cx"] intValue]; + registers.dx_.full = [initial_registers[@"dx"] intValue]; + + registers.bp_ = [initial_registers[@"bp"] intValue]; + registers.cs_ = [initial_registers[@"cs"] intValue]; + registers.di_ = [initial_registers[@"di"] intValue]; + registers.ds_ = [initial_registers[@"ds"] intValue]; + registers.es_ = [initial_registers[@"es"] intValue]; + registers.si_ = [initial_registers[@"si"] intValue]; + registers.sp_ = [initial_registers[@"sp"] intValue]; + registers.ss_ = [initial_registers[@"ss"] intValue]; + registers.ip_ = [initial_registers[@"ip"] intValue]; + + status.set([initial_registers[@"flags"] intValue]); + InstructionSet::x86::perform( decoded.second, status, From c6b311b84ab56643bcbdd66c1233fc5d19d0c061 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 6 Oct 2023 11:10:54 -0400 Subject: [PATCH 014/128] Explain source of comments. --- .../x86/Implementation/PerformImplementation.hpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/InstructionSets/x86/Implementation/PerformImplementation.hpp b/InstructionSets/x86/Implementation/PerformImplementation.hpp index eb879db40..09c49cbaf 100644 --- a/InstructionSets/x86/Implementation/PerformImplementation.hpp +++ b/InstructionSets/x86/Implementation/PerformImplementation.hpp @@ -55,6 +55,14 @@ IntT overflow(IntT lhs, IntT rhs, IntT result) { // END COPY AND PASTE SECTION. // +// +// Comments below on intended functioning of each operation come from the 1997 edition of the +// Intel Architecture Software Developer’s Manual; that year all such definitions still fitted within a +// single volume, Volume 2. +// +// Order Number 243191; e.g. https://www.ardent-tool.com/CPU/docs/Intel/IA/243191-002.pdf +// + void aaa(CPU::RegisterPair16 &ax, Status &status) { /* IF ((AL AND 0FH) > 9) OR (AF = 1) From a0ca0bb3c04cfe424318d01e3e95a508eeaeb281 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 6 Oct 2023 11:11:29 -0400 Subject: [PATCH 015/128] Mark non-templates as inline. --- .../x86/Implementation/PerformImplementation.hpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/InstructionSets/x86/Implementation/PerformImplementation.hpp b/InstructionSets/x86/Implementation/PerformImplementation.hpp index 09c49cbaf..3893a22f0 100644 --- a/InstructionSets/x86/Implementation/PerformImplementation.hpp +++ b/InstructionSets/x86/Implementation/PerformImplementation.hpp @@ -63,7 +63,7 @@ IntT overflow(IntT lhs, IntT rhs, IntT result) { // Order Number 243191; e.g. https://www.ardent-tool.com/CPU/docs/Intel/IA/243191-002.pdf // -void aaa(CPU::RegisterPair16 &ax, Status &status) { +inline void aaa(CPU::RegisterPair16 &ax, Status &status) { /* IF ((AL AND 0FH) > 9) OR (AF = 1) THEN @@ -91,7 +91,7 @@ void aaa(CPU::RegisterPair16 &ax, Status &status) { ax.halves.low &= 0x0f; } -void aad(CPU::RegisterPair16 &ax, uint8_t imm, Status &status) { +inline void aad(CPU::RegisterPair16 &ax, uint8_t imm, Status &status) { /* tempAL ← AL; tempAH ← AH; @@ -108,7 +108,7 @@ void aad(CPU::RegisterPair16 &ax, uint8_t imm, Status &status) { status.parity = status.zero = ax.halves.low; } -void aam(CPU::RegisterPair16 &ax, uint8_t imm, Status &status) { +inline void aam(CPU::RegisterPair16 &ax, uint8_t imm, Status &status) { /* tempAL ← AL; AH ← tempAL / imm8; (* imm8 is set to 0AH for the AAD mnemonic *) @@ -124,7 +124,7 @@ void aam(CPU::RegisterPair16 &ax, uint8_t imm, Status &status) { status.parity = status.zero = ax.halves.low; } -void aas(CPU::RegisterPair16 &ax, Status &status) { +inline void aas(CPU::RegisterPair16 &ax, Status &status) { /* IF ((AL AND 0FH) > 9) OR (AF = 1) THEN From 2d17d9d316572ab9843afaf09cfdabc79716f23e Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 6 Oct 2023 11:31:45 -0400 Subject: [PATCH 016/128] Execute some tests at some facile level. --- .../Implementation/PerformImplementation.hpp | 2 +- InstructionSets/x86/Status.hpp | 9 + OSBindings/Mac/Clock SignalTests/8088Tests.mm | 225 +++++++++++------- 3 files changed, 151 insertions(+), 85 deletions(-) diff --git a/InstructionSets/x86/Implementation/PerformImplementation.hpp b/InstructionSets/x86/Implementation/PerformImplementation.hpp index 3893a22f0..072d59e53 100644 --- a/InstructionSets/x86/Implementation/PerformImplementation.hpp +++ b/InstructionSets/x86/Implementation/PerformImplementation.hpp @@ -63,7 +63,7 @@ IntT overflow(IntT lhs, IntT rhs, IntT result) { // 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) { +inline void aaa(CPU::RegisterPair16 &ax, Status &status) { // P. 313 /* IF ((AL AND 0FH) > 9) OR (AF = 1) THEN diff --git a/InstructionSets/x86/Status.hpp b/InstructionSets/x86/Status.hpp index b1c4b2e1e..57cc9157e 100644 --- a/InstructionSets/x86/Status.hpp +++ b/InstructionSets/x86/Status.hpp @@ -74,6 +74,15 @@ struct Status { // TODO: the rest. } + + uint16_t get() const { + return + (carry ? ConditionCode::Carry : 0); + } + + bool operator ==(const Status &rhs) const { + return get() == rhs.get(); + } }; } diff --git a/OSBindings/Mac/Clock SignalTests/8088Tests.mm b/OSBindings/Mac/Clock SignalTests/8088Tests.mm index 7c5f53033..25748663a 100644 --- a/OSBindings/Mac/Clock SignalTests/8088Tests.mm +++ b/OSBindings/Mac/Clock SignalTests/8088Tests.mm @@ -27,6 +27,87 @@ namespace { // provide their real path here. constexpr char TestSuiteHome[] = "/Users/tharte/Projects/ProcessorTests/8088/v1"; +struct Registers { + CPU::RegisterPair16 ax_; + uint8_t &al() { return ax_.halves.low; } + uint8_t &ah() { return ax_.halves.high; } + uint16_t &ax() { return ax_.full; } + + CPU::RegisterPair16 &axp() { return ax_; } + + CPU::RegisterPair16 cx_; + uint8_t &cl() { return cx_.halves.low; } + uint8_t &ch() { return cx_.halves.high; } + uint16_t &cx() { return cx_.full; } + + CPU::RegisterPair16 dx_; + uint8_t &dl() { return dx_.halves.low; } + uint8_t &dh() { return dx_.halves.high; } + uint16_t &dx() { return dx_.full; } + + CPU::RegisterPair16 bx_; + uint8_t &bl() { return bx_.halves.low; } + uint8_t &bh() { return bx_.halves.high; } + uint16_t &bx() { return bx_.full; } + + uint16_t sp_; + uint16_t &sp() { return sp_; } + + uint16_t bp_; + uint16_t &bp() { return bp_; } + + uint16_t si_; + uint16_t &si() { return si_; } + + uint16_t di_; + uint16_t &di() { return di_; } + + uint16_t es_, cs_, ds_, ss_; + uint16_t ip_; + + bool operator ==(const Registers &rhs) const { + return + ax_.full == rhs.ax_.full && + cx_.full == rhs.cx_.full && + dx_.full == rhs.dx_.full && + bx_.full == rhs.bx_.full && + sp_ == rhs.sp_ && + bp_ == rhs.bp_ && + si_ == rhs.si_ && + di_ == rhs.di_ && + es_ == rhs.es_ && + cs_ == rhs.cs_ && + ds_ == rhs.ds_ && + si_ == rhs.si_ && + ip_ == rhs.ip_; + } +}; +struct Memory { + std::vector memory; + const Registers ®isters_; + + Memory(Registers ®isters) : registers_(registers) { + memory.resize(1024*1024); + } + + template IntT &access([[maybe_unused]] InstructionSet::x86::Source segment, uint16_t address) { + 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::DS: physical_address = registers_.ds_; break; + } + physical_address = ((physical_address << 4) + address) & 0xf'ffff; + return *reinterpret_cast(&memory[physical_address]); + } +}; +struct IO { +}; +struct FlowController { +}; + } @interface i8088Tests : XCTestCase @@ -37,6 +118,7 @@ constexpr char TestSuiteHome[] = "/Users/tharte/Projects/ProcessorTests/8088/v1" - (NSArray *)testFiles { NSString *path = [NSString stringWithUTF8String:TestSuiteHome]; NSSet *allowList = [NSSet setWithArray:@[ + @"D4.json.gz" ]]; NSSet *ignoreList = nil; @@ -168,75 +250,30 @@ constexpr char TestSuiteHome[] = "/Users/tharte/Projects/ProcessorTests/8088/v1" return isEqual; } +- (void)populate:(Registers &)registers status:(InstructionSet::x86::Status &)status value:(NSDictionary *)value { + registers.ax_.full = [value[@"ax"] intValue]; + registers.bx_.full = [value[@"bx"] intValue]; + registers.cx_.full = [value[@"cx"] intValue]; + registers.dx_.full = [value[@"dx"] intValue]; + + registers.bp_ = [value[@"bp"] intValue]; + registers.cs_ = [value[@"cs"] intValue]; + registers.di_ = [value[@"di"] intValue]; + registers.ds_ = [value[@"ds"] intValue]; + registers.es_ = [value[@"es"] intValue]; + registers.si_ = [value[@"si"] intValue]; + registers.sp_ = [value[@"sp"] intValue]; + registers.ss_ = [value[@"ss"] intValue]; + registers.ip_ = [value[@"ip"] intValue]; + + status.set([value[@"flags"] intValue]); +} + - (bool)applyExecutionTest:(NSDictionary *)test file:(NSString *)file assert:(BOOL)assert { InstructionSet::x86::Decoder decoder; const auto data = [self bytes:test[@"bytes"]]; const auto decoded = decoder.decode(data.data(), data.size()); - struct Registers { - CPU::RegisterPair16 ax_; - uint8_t &al() { return ax_.halves.low; } - uint8_t &ah() { return ax_.halves.high; } - uint16_t &ax() { return ax_.full; } - - CPU::RegisterPair16 &axp() { return ax_; } - - CPU::RegisterPair16 cx_; - uint8_t &cl() { return cx_.halves.low; } - uint8_t &ch() { return cx_.halves.high; } - uint16_t &cx() { return cx_.full; } - - CPU::RegisterPair16 dx_; - uint8_t &dl() { return dx_.halves.low; } - uint8_t &dh() { return dx_.halves.high; } - uint16_t &dx() { return dx_.full; } - - CPU::RegisterPair16 bx_; - uint8_t &bl() { return bx_.halves.low; } - uint8_t &bh() { return bx_.halves.high; } - uint16_t &bx() { return bx_.full; } - - uint16_t sp_; - uint16_t &sp() { return sp_; } - - uint16_t bp_; - uint16_t &bp() { return bp_; } - - uint16_t si_; - uint16_t &si() { return si_; } - - uint16_t di_; - uint16_t &di() { return di_; } - - uint16_t es_, cs_, ds_, ss_; - uint16_t ip_; - }; - struct Memory { - std::vector memory; - const Registers ®isters_; - - Memory(Registers ®isters) : registers_(registers) { - memory.resize(1024*1024); - } - - template IntT &access([[maybe_unused]] InstructionSet::x86::Source segment, uint16_t address) { - 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::DS: physical_address = registers_.ds_; break; - } - physical_address = ((physical_address << 4) + address) & 0xf'ffff; - return *reinterpret_cast(&memory[physical_address]); - } - }; - struct IO { - }; - struct FlowController { - }; - InstructionSet::x86::Status status; FlowController flow_controller; Registers registers; @@ -244,28 +281,14 @@ constexpr char TestSuiteHome[] = "/Users/tharte/Projects/ProcessorTests/8088/v1" IO io; // Apply initial state. - NSDictionary *const initial = test[@"initial"]; - for(NSArray *ram in initial[@"ram"]) { + NSDictionary *const initial_state = test[@"initial"]; + for(NSArray *ram in initial_state[@"ram"]) { memory.memory[[ram[0] intValue]] = [ram[1] intValue]; } - NSDictionary *const initial_registers = initial[@"regs"]; - registers.ax_.full = [initial_registers[@"ax"] intValue]; - registers.bx_.full = [initial_registers[@"bx"] intValue]; - registers.cx_.full = [initial_registers[@"cx"] intValue]; - registers.dx_.full = [initial_registers[@"dx"] intValue]; - - registers.bp_ = [initial_registers[@"bp"] intValue]; - registers.cs_ = [initial_registers[@"cs"] intValue]; - registers.di_ = [initial_registers[@"di"] intValue]; - registers.ds_ = [initial_registers[@"ds"] intValue]; - registers.es_ = [initial_registers[@"es"] intValue]; - registers.si_ = [initial_registers[@"si"] intValue]; - registers.sp_ = [initial_registers[@"sp"] intValue]; - registers.ss_ = [initial_registers[@"ss"] intValue]; - registers.ip_ = [initial_registers[@"ip"] intValue]; - - status.set([initial_registers[@"flags"] intValue]); + [self populate:registers status:status value:initial_state[@"regs"]]; + // Execute instruction. + registers.ip_ += decoded.first; InstructionSet::x86::perform( decoded.second, status, @@ -275,7 +298,41 @@ constexpr char TestSuiteHome[] = "/Users/tharte/Projects/ProcessorTests/8088/v1" io ); - return false; + // Compare final state. + NSDictionary *const final_state = test[@"final"]; + Registers intended_registers; + InstructionSet::x86::Status intended_status; + + bool ramEqual = true; + for(NSArray *ram in final_state[@"ram"]) { + ramEqual &= memory.memory[[ram[0] intValue]] == [ram[1] intValue]; + } + + [self populate:intended_registers status:intended_status value:final_state[@"regs"]]; + const bool registersEqual = intended_registers == registers; + const bool statusEqual = intended_status == status; + + if(assert) { + XCTAssert( + statusEqual, + "Status doesn't match — differs in %02x after %@", + intended_status.get() ^ status.get(), + test[@"name"] + ); + // TODO: should probably say more about the following two. + XCTAssert( + registersEqual, + "Register mismatch after %@", + test[@"name"] + ); + XCTAssert( + ramEqual, + "Memory contents mismatch after %@", + test[@"name"] + ); + } + + return statusEqual && registersEqual && ramEqual; } - (void)printFailures:(NSArray *)failures { From 82f0cd790fe2047b15668cf7735a8837c7817a18 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 6 Oct 2023 11:43:18 -0400 Subject: [PATCH 017/128] Find first failing execution, note reason. --- .../x86/Implementation/PerformImplementation.hpp | 5 +++++ OSBindings/Mac/Clock SignalTests/8088Tests.mm | 15 +++++++++------ 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/InstructionSets/x86/Implementation/PerformImplementation.hpp b/InstructionSets/x86/Implementation/PerformImplementation.hpp index 072d59e53..6f4a8df92 100644 --- a/InstructionSets/x86/Implementation/PerformImplementation.hpp +++ b/InstructionSets/x86/Implementation/PerformImplementation.hpp @@ -118,10 +118,15 @@ inline void aam(CPU::RegisterPair16 &ax, uint8_t imm, Status &status) { The SF, ZF, and PF flags are set according to the result. The OF, AF, and CF flags are undefined. */ + /* + If ... an immediate value of 0 is used, it will cause a #DE (divide error) exception. + */ ax.halves.high = ax.halves.low / imm; ax.halves.low = ax.halves.low % imm; status.sign = ax.halves.low & 0x80; status.parity = status.zero = ax.halves.low; + + // TODO: #DE. } inline void aas(CPU::RegisterPair16 &ax, Status &status) { diff --git a/OSBindings/Mac/Clock SignalTests/8088Tests.mm b/OSBindings/Mac/Clock SignalTests/8088Tests.mm index 25748663a..296beca58 100644 --- a/OSBindings/Mac/Clock SignalTests/8088Tests.mm +++ b/OSBindings/Mac/Clock SignalTests/8088Tests.mm @@ -315,20 +315,23 @@ struct FlowController { if(assert) { XCTAssert( statusEqual, - "Status doesn't match — differs in %02x after %@", + "Status doesn't match — differs in %02x after %@; executing %@", intended_status.get() ^ status.get(), - test[@"name"] + test[@"name"], + [self toString:decoded.second offsetLength:4 immediateLength:4] ); // TODO: should probably say more about the following two. XCTAssert( registersEqual, - "Register mismatch after %@", - test[@"name"] + "Register mismatch after %@; executing %@", + test[@"name"], + [self toString:decoded.second offsetLength:4 immediateLength:4] ); XCTAssert( ramEqual, - "Memory contents mismatch after %@", - test[@"name"] + "Memory contents mismatch after %@; executing %@", + test[@"name"], + [self toString:decoded.second offsetLength:4 immediateLength:4] ); } From b6d000ac5e154c234b2fe6dfe3a590c40abec3bc Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 6 Oct 2023 13:22:35 -0400 Subject: [PATCH 018/128] Add enough wiring to consolidate failure on lazy handling of flags. --- .../Implementation/PerformImplementation.hpp | 23 ++++++---- InstructionSets/x86/Interrupts.hpp | 24 ++++++++++ .../Clock Signal.xcodeproj/project.pbxproj | 2 + OSBindings/Mac/Clock SignalTests/8088Tests.mm | 46 +++++++++++++++++-- 4 files changed, 82 insertions(+), 13 deletions(-) create mode 100644 InstructionSets/x86/Interrupts.hpp diff --git a/InstructionSets/x86/Implementation/PerformImplementation.hpp b/InstructionSets/x86/Implementation/PerformImplementation.hpp index 6f4a8df92..a5c427c22 100644 --- a/InstructionSets/x86/Implementation/PerformImplementation.hpp +++ b/InstructionSets/x86/Implementation/PerformImplementation.hpp @@ -11,6 +11,7 @@ #include "../../../Numeric/Carry.hpp" #include "../../../Numeric/RegisterSizes.hpp" +#include "../Interrupts.hpp" namespace InstructionSet::x86 { @@ -108,7 +109,8 @@ inline void aad(CPU::RegisterPair16 &ax, uint8_t imm, Status &status) { status.parity = status.zero = ax.halves.low; } -inline void aam(CPU::RegisterPair16 &ax, uint8_t imm, Status &status) { +template +inline void aam(CPU::RegisterPair16 &ax, uint8_t imm, Status &status, FlowControllerT &flow_controller) { /* tempAL ← AL; AH ← tempAL / imm8; (* imm8 is set to 0AH for the AAD mnemonic *) @@ -121,12 +123,15 @@ inline void aam(CPU::RegisterPair16 &ax, uint8_t imm, Status &status) { /* If ... an immediate value of 0 is used, it will cause a #DE (divide error) exception. */ + if(!imm) { + flow_controller.interrupt(Interrupt::DivideByZero); + return; + } + ax.halves.high = ax.halves.low / imm; ax.halves.low = ax.halves.low % imm; status.sign = ax.halves.low & 0x80; status.parity = status.zero = ax.halves.low; - - // TODO: #DE. } inline void aas(CPU::RegisterPair16 &ax, Status &status) { @@ -303,13 +308,13 @@ template < default: return; //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); return; - case Operation::AAS: Primitive::aas(registers.axp(), status); return; + 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::ADC: Primitive::adc(destination(), source(), status); break; - case Operation::ADD: Primitive::add(destination(), source(), status); break; + case Operation::ADC: Primitive::adc(destination(), source(), status); break; + case Operation::ADD: Primitive::add(destination(), source(), status); break; } // Write to memory if required to complete this operation. diff --git a/InstructionSets/x86/Interrupts.hpp b/InstructionSets/x86/Interrupts.hpp new file mode 100644 index 000000000..0fb424999 --- /dev/null +++ b/InstructionSets/x86/Interrupts.hpp @@ -0,0 +1,24 @@ +// +// Interrupts.hpp +// Clock Signal +// +// Created by Thomas Harte on 06/10/2023. +// Copyright © 2023 Thomas Harte. All rights reserved. +// + +#ifndef InstructionSets_M68k_Interrupts_h +#define InstructionSets_M68k_Interrupts_h + +namespace InstructionSet::x86 { + +enum Interrupt { + DivideByZero = 0, + SingleStep = 1, + NMI = 2, + OneByte = 3, + OnOverflow = 4, +}; + +} + +#endif /* InstructionSets_M68k_Interrupts_h */ diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index 1f67f4bdb..a7ef975c3 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -1129,6 +1129,7 @@ 42437B342ACF02A9006DFED1 /* Status.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Status.hpp; sourceTree = ""; }; 42437B352ACF0AA2006DFED1 /* Perform.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Perform.hpp; sourceTree = ""; }; 42437B382ACF2798006DFED1 /* PerformImplementation.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = PerformImplementation.hpp; sourceTree = ""; }; + 42437B392AD07465006DFED1 /* Interrupts.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Interrupts.hpp; sourceTree = ""; }; 4281572E2AA0334300E16AA1 /* Carry.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Carry.hpp; sourceTree = ""; }; 428168372A16C25C008ECD27 /* LineLayout.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = LineLayout.hpp; sourceTree = ""; }; 428168392A37AFB4008ECD27 /* DispatcherTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = DispatcherTests.mm; sourceTree = ""; }; @@ -5007,6 +5008,7 @@ 42437B352ACF0AA2006DFED1 /* Perform.hpp */, 42437B342ACF02A9006DFED1 /* Status.hpp */, 42437B372ACF2798006DFED1 /* Implementation */, + 42437B392AD07465006DFED1 /* Interrupts.hpp */, ); path = x86; sourceTree = ""; diff --git a/OSBindings/Mac/Clock SignalTests/8088Tests.mm b/OSBindings/Mac/Clock SignalTests/8088Tests.mm index 296beca58..f080c0475 100644 --- a/OSBindings/Mac/Clock SignalTests/8088Tests.mm +++ b/OSBindings/Mac/Clock SignalTests/8088Tests.mm @@ -27,6 +27,7 @@ namespace { // provide their real path here. constexpr char TestSuiteHome[] = "/Users/tharte/Projects/ProcessorTests/8088/v1"; +using Status = InstructionSet::x86::Status; struct Registers { CPU::RegisterPair16 ax_; uint8_t &al() { return ax_.halves.low; } @@ -97,15 +98,52 @@ struct Memory { default: physical_address = registers_.ds_; break; case Source::ES: physical_address = registers_.es_; break; case Source::CS: physical_address = registers_.cs_; break; - case Source::DS: physical_address = registers_.ds_; break; + case Source::SS: physical_address = registers_.ss_; break; } physical_address = ((physical_address << 4) + address) & 0xf'ffff; - return *reinterpret_cast(&memory[physical_address]); + return access(physical_address); + } + + template IntT &access(uint32_t address) { + return *reinterpret_cast(&memory[address]); } }; struct IO { }; -struct FlowController { +class FlowController { + public: + FlowController(Memory &memory, Registers ®isters, Status &status) : + memory_(memory), registers_(registers), status_(status) {} + + void interrupt(int index) { + const uint16_t address = static_cast(index) << 2; + const uint16_t new_ip = memory_.access(address); + const uint16_t new_cs = memory_.access(address + 2); + + push(status_.get()); + + // TODO: set I and TF +// status_. + + // Push CS and IP. + push(registers_.cs_); + push(registers_.ip_); + + registers_.cs_ = new_cs; + registers_.ip_ = new_ip; + } + + private: + Memory &memory_; + Registers ®isters_; + Status status_; + + void push(uint16_t value) { + --registers_.sp_; + memory_.access(InstructionSet::x86::Source::SS, registers_.sp_) = value >> 8; + --registers_.sp_; + memory_.access(InstructionSet::x86::Source::SS, registers_.sp_) = value & 0xff; + } }; } @@ -275,9 +313,9 @@ struct FlowController { const auto decoded = decoder.decode(data.data(), data.size()); InstructionSet::x86::Status status; - FlowController flow_controller; Registers registers; Memory memory(registers); + FlowController flow_controller(memory, registers, status); IO io; // Apply initial state. From cf4603cb33109b922ddf16f65fcecba6fc00deac Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 6 Oct 2023 16:32:35 -0400 Subject: [PATCH 019/128] Attempt to check defined flags only. --- InstructionSets/x86/Status.hpp | 41 +++++++- OSBindings/Mac/Clock SignalTests/8088Tests.mm | 99 +++++++++++++++---- 2 files changed, 115 insertions(+), 25 deletions(-) diff --git a/InstructionSets/x86/Status.hpp b/InstructionSets/x86/Status.hpp index 57cc9157e..9db5c087a 100644 --- a/InstructionSets/x86/Status.hpp +++ b/InstructionSets/x86/Status.hpp @@ -59,6 +59,9 @@ struct Status { uint32_t auxiliary_carry; uint32_t sign; uint32_t overflow; + uint32_t trap; + uint32_t interrupt; + uint32_t direction; // Zero => set; non-zero => unset. uint32_t zero; @@ -66,18 +69,48 @@ struct Status { // Odd number of bits => set; even => unset. uint32_t parity; - // Convenience getters. - template IntT carry_bit() { return carry ? 1 : 0; } + // Flag getters. + template IntT carry_bit() const { return carry ? 1 : 0; } + bool parity_bit() const { + uint32_t result = parity; + result ^= result >> 16; + result ^= result >> 8; + result ^= result >> 4; + result ^= result >> 2; + result ^= result >> 1; + return 1 ^ (result & 1); + } + // Complete value get and set. void set(uint16_t value) { carry = value & ConditionCode::Carry; + auxiliary_carry = value & ConditionCode::AuxiliaryCarry; + sign = value & ConditionCode::Sign; + overflow = value & ConditionCode::Overflow; + trap = value & ConditionCode::Trap; + interrupt = value & ConditionCode::Interrupt; + direction = value & ConditionCode::Direction; - // TODO: the rest. + zero = (~value) & ConditionCode::Zero; + + parity = (~value) & ConditionCode::Parity; } uint16_t get() const { return - (carry ? ConditionCode::Carry : 0); + 0xf002 | + + (carry ? ConditionCode::Carry : 0) | + (auxiliary_carry ? ConditionCode::AuxiliaryCarry : 0) | + (sign ? ConditionCode::Sign : 0) | + (overflow ? ConditionCode::Overflow : 0) | + (trap ? ConditionCode::Trap : 0) | + (interrupt ? ConditionCode::Interrupt : 0) | + (direction ? ConditionCode::Direction : 0) | + + (zero ? 0 : ConditionCode::Zero) | + + (parity_bit() ? ConditionCode::Parity : 0); } bool operator ==(const Status &rhs) const { diff --git a/OSBindings/Mac/Clock SignalTests/8088Tests.mm b/OSBindings/Mac/Clock SignalTests/8088Tests.mm index f080c0475..881872d82 100644 --- a/OSBindings/Mac/Clock SignalTests/8088Tests.mm +++ b/OSBindings/Mac/Clock SignalTests/8088Tests.mm @@ -84,6 +84,13 @@ struct Registers { } }; struct Memory { + enum class Tag { + Accessed, + FlagsL, + FlagsH + }; + + std::unordered_map tags; std::vector memory; const Registers ®isters_; @@ -91,7 +98,9 @@ struct Memory { memory.resize(1024*1024); } - template IntT &access([[maybe_unused]] InstructionSet::x86::Source segment, uint16_t address) { + // 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([[maybe_unused]] InstructionSet::x86::Source segment, uint16_t address, Tag tag) { uint32_t physical_address; using Source = InstructionSet::x86::Source; switch(segment) { @@ -101,12 +110,20 @@ struct Memory { case Source::SS: physical_address = registers_.ss_; break; } physical_address = ((physical_address << 4) + address) & 0xf'ffff; - return access(physical_address); + return access(physical_address, tag); } - template IntT &access(uint32_t address) { + // 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) { + tags[address] = tag; return *reinterpret_cast(&memory[address]); } + + // Entry point for the 8086; simply notes that memory was accessed. + template IntT &access([[maybe_unused]] InstructionSet::x86::Source segment, uint32_t address) { + return access(segment, address, Tag::Accessed); + } }; struct IO { }; @@ -117,13 +134,13 @@ class FlowController { void interrupt(int index) { const uint16_t address = static_cast(index) << 2; - const uint16_t new_ip = memory_.access(address); - const uint16_t new_cs = memory_.access(address + 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()); + push(status_.get(), true); - // TODO: set I and TF -// status_. + status_.interrupt = 0; + status_.trap = 0; // Push CS and IP. push(registers_.cs_); @@ -136,13 +153,23 @@ class FlowController { private: Memory &memory_; Registers ®isters_; - Status status_; + Status &status_; - void push(uint16_t value) { + 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_) = value >> 8; + 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_) = value & 0xff; + memory_.access( + InstructionSet::x86::Source::SS, + registers_.sp_, + is_flags ? Memory::Tag::FlagsL : Memory::Tag::Accessed + ) = value & 0xff; } }; @@ -185,6 +212,11 @@ class FlowController { return [NSJSONSerialization JSONObjectWithData:data options:0 error:nil]; } +- (NSDictionary *)metadata { + NSString *path = [[NSString stringWithUTF8String:TestSuiteHome] stringByAppendingPathComponent:@"8088.json"]; + return [NSJSONSerialization JSONObjectWithData:[NSData dataWithContentsOfGZippedFile:path] options:0 error:nil]; +} + - (NSString *)toString:(const InstructionSet::x86::Instruction &)instruction offsetLength:(int)offsetLength immediateLength:(int)immediateLength { const auto operation = to_string(instruction, InstructionSet::x86::Model::i8086, offsetLength, immediateLength); return [[NSString stringWithUTF8String:operation.c_str()] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; @@ -307,23 +339,28 @@ class FlowController { status.set([value[@"flags"] intValue]); } -- (bool)applyExecutionTest:(NSDictionary *)test file:(NSString *)file assert:(BOOL)assert { +- (bool)applyExecutionTest:(NSDictionary *)test file:(NSString *)file metadata:(NSDictionary *)metadata assert:(BOOL)assert { InstructionSet::x86::Decoder decoder; const auto data = [self bytes:test[@"bytes"]]; const auto decoded = decoder.decode(data.data(), data.size()); - InstructionSet::x86::Status status; + InstructionSet::x86::Status initial_status, status; Registers registers; Memory memory(registers); FlowController flow_controller(memory, registers, status); IO io; + const uint16_t flags_mask = metadata[@"flags-mask"] ? [metadata[@"flags-mask"] intValue] : 0xffff; + // Apply initial state. NSDictionary *const initial_state = test[@"initial"]; for(NSArray *ram in initial_state[@"ram"]) { memory.memory[[ram[0] intValue]] = [ram[1] intValue]; } - [self populate:registers status:status value:initial_state[@"regs"]]; + [self populate:registers status:initial_status value:initial_state[@"regs"]]; + status = initial_status; + + NSLog(@"Initial status: %04x as per %@", status.get(), initial_state[@"regs"][@"flags"]); // Execute instruction. registers.ip_ += decoded.first; @@ -343,18 +380,30 @@ class FlowController { bool ramEqual = true; for(NSArray *ram in final_state[@"ram"]) { - ramEqual &= memory.memory[[ram[0] intValue]] == [ram[1] intValue]; + const uint32_t address = [ram[0] intValue]; + + uint8_t mask = 0xff; + if(const auto tag = memory.tags.find(address); tag != 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; + } + } + + ramEqual &= (memory.memory[address] & mask) == ([ram[1] intValue] & mask); } [self populate:intended_registers status:intended_status value:final_state[@"regs"]]; const bool registersEqual = intended_registers == registers; - const bool statusEqual = intended_status == status; + const bool statusEqual = (intended_status.get() & flags_mask) == (status.get() & flags_mask); if(assert) { XCTAssert( statusEqual, - "Status doesn't match — differs in %02x after %@; executing %@", - intended_status.get() ^ status.get(), + "Status doesn't match despite mask %04x — differs in %04x after %@; executing %@", + flags_mask, + (intended_status.get() ^ status.get()) & flags_mask, test[@"name"], [self toString:decoded.second offsetLength:4 immediateLength:4] ); @@ -403,15 +452,23 @@ class FlowController { } - (void)testExecution { + NSDictionary *metadata = [self metadata]; + NSMutableArray *failures = [[NSMutableArray alloc] init]; for(NSString *file in [self testFiles]) { + // Determine what the metadata key. + NSString *const name = [file lastPathComponent]; + NSRange first_dot = [name rangeOfString:@"."]; + NSString *metadata_key = [name substringToIndex:first_dot.location]; + + NSDictionary *test_metadata = metadata[metadata_key]; for(NSDictionary *test in [self testsInFile:file]) { // A single failure per instruction is fine. - if(![self applyExecutionTest:test file:file assert:YES]) { + if(![self applyExecutionTest:test file:file metadata:test_metadata assert:YES]) { [failures addObject:file]; // Attempt a second decoding, to provide a debugger hook. - [self applyExecutionTest:test file:file assert:NO]; + [self applyExecutionTest:test file:file metadata:test_metadata assert:NO]; break; } } From 16bf7c6f263461da92a40fa058215d79ef36f515 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 7 Oct 2023 13:31:35 -0400 Subject: [PATCH 020/128] Fix include guard. --- InstructionSets/x86/Interrupts.hpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/InstructionSets/x86/Interrupts.hpp b/InstructionSets/x86/Interrupts.hpp index 0fb424999..c8d1dcb68 100644 --- a/InstructionSets/x86/Interrupts.hpp +++ b/InstructionSets/x86/Interrupts.hpp @@ -6,8 +6,8 @@ // Copyright © 2023 Thomas Harte. All rights reserved. // -#ifndef InstructionSets_M68k_Interrupts_h -#define InstructionSets_M68k_Interrupts_h +#ifndef InstructionSets_x86_Interrupts_h +#define InstructionSets_x86_Interrupts_h namespace InstructionSet::x86 { @@ -21,4 +21,4 @@ enum Interrupt { } -#endif /* InstructionSets_M68k_Interrupts_h */ +#endif /* InstructionSets_x86_Interrupts_h */ From 5c62606154d7956e925bdd99a4de2e7e6a54a9b0 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 7 Oct 2023 13:38:36 -0400 Subject: [PATCH 021/128] Simplify parity logic. --- InstructionSets/x86/Status.hpp | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/InstructionSets/x86/Status.hpp b/InstructionSets/x86/Status.hpp index 9db5c087a..81e229335 100644 --- a/InstructionSets/x86/Status.hpp +++ b/InstructionSets/x86/Status.hpp @@ -71,14 +71,13 @@ struct Status { // Flag getters. template IntT carry_bit() const { return carry ? 1 : 0; } - bool parity_bit() const { - uint32_t result = parity; - result ^= result >> 16; - result ^= result >> 8; + bool not_parity_bit() const { + // x86 parity always considers the lowest 8-bits only. + auto result = static_cast(parity); result ^= result >> 4; result ^= result >> 2; result ^= result >> 1; - return 1 ^ (result & 1); + return result & 1; } // Complete value get and set. @@ -110,7 +109,7 @@ struct Status { (zero ? 0 : ConditionCode::Zero) | - (parity_bit() ? ConditionCode::Parity : 0); + (not_parity_bit() ? 0 : ConditionCode::Parity); } bool operator ==(const Status &rhs) const { From ade5828035cdd8b3c00dc6313173ca84dc8e7678 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 7 Oct 2023 13:39:23 -0400 Subject: [PATCH 022/128] Add a `clear`, in the hope of not recreating `Memory` every test. It's a big allocation, and therefore likely the bottleneck on test running. --- OSBindings/Mac/Clock SignalTests/8088Tests.mm | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/OSBindings/Mac/Clock SignalTests/8088Tests.mm b/OSBindings/Mac/Clock SignalTests/8088Tests.mm index 881872d82..ba5071bb8 100644 --- a/OSBindings/Mac/Clock SignalTests/8088Tests.mm +++ b/OSBindings/Mac/Clock SignalTests/8088Tests.mm @@ -98,6 +98,10 @@ struct Memory { memory.resize(1024*1024); } + 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 IntT &access([[maybe_unused]] InstructionSet::x86::Source segment, uint16_t address, Tag tag) { @@ -360,8 +364,6 @@ class FlowController { [self populate:registers status:initial_status value:initial_state[@"regs"]]; status = initial_status; - NSLog(@"Initial status: %04x as per %@", status.get(), initial_state[@"regs"][@"flags"]); - // Execute instruction. registers.ip_ += decoded.first; InstructionSet::x86::perform( From 7d093d71b33c147b8f466db2b575dc756669e83e Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 7 Oct 2023 14:23:47 -0400 Subject: [PATCH 023/128] Avoid allocating and reallocating per test. --- OSBindings/Mac/Clock SignalTests/8088Tests.mm | 56 ++++++++++++------- 1 file changed, 35 insertions(+), 21 deletions(-) diff --git a/OSBindings/Mac/Clock SignalTests/8088Tests.mm b/OSBindings/Mac/Clock SignalTests/8088Tests.mm index ba5071bb8..b5383fa76 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 { @@ -177,12 +177,29 @@ class FlowController { } }; +struct ExecutionSupport { + InstructionSet::x86::Status status; + Registers registers; + Memory memory; + FlowController flow_controller; + IO io; + + ExecutionSupport() : memory(registers), flow_controller(memory, registers, status) {} + + void clear() { + memory.clear(); + } +}; + + } @interface i8088Tests : XCTestCase @end -@implementation i8088Tests +@implementation i8088Tests { + ExecutionSupport execution_support; +} - (NSArray *)testFiles { NSString *path = [NSString stringWithUTF8String:TestSuiteHome]; @@ -348,31 +365,28 @@ class FlowController { const auto data = [self bytes:test[@"bytes"]]; const auto decoded = decoder.decode(data.data(), data.size()); - InstructionSet::x86::Status initial_status, status; - Registers registers; - Memory memory(registers); - FlowController flow_controller(memory, registers, status); - IO io; + execution_support.clear(); const uint16_t flags_mask = metadata[@"flags-mask"] ? [metadata[@"flags-mask"] intValue] : 0xffff; // Apply initial state. NSDictionary *const initial_state = test[@"initial"]; + InstructionSet::x86::Status initial_status; for(NSArray *ram in initial_state[@"ram"]) { - memory.memory[[ram[0] intValue]] = [ram[1] intValue]; + execution_support.memory.memory[[ram[0] intValue]] = [ram[1] intValue]; } - [self populate:registers status:initial_status value:initial_state[@"regs"]]; - status = initial_status; + [self populate:execution_support.registers status:initial_status value:initial_state[@"regs"]]; + execution_support.status = initial_status; // Execute instruction. - registers.ip_ += decoded.first; + execution_support.registers.ip_ += decoded.first; InstructionSet::x86::perform( decoded.second, - status, - flow_controller, - registers, - memory, - io + execution_support.status, + execution_support.flow_controller, + execution_support.registers, + execution_support.memory, + execution_support.io ); // Compare final state. @@ -385,7 +399,7 @@ class FlowController { const uint32_t address = [ram[0] intValue]; uint8_t mask = 0xff; - if(const auto tag = memory.tags.find(address); tag != memory.tags.end()) { + 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; @@ -393,19 +407,19 @@ class FlowController { } } - ramEqual &= (memory.memory[address] & mask) == ([ram[1] intValue] & mask); + ramEqual &= (execution_support.memory.memory[address] & mask) == ([ram[1] intValue] & mask); } [self populate:intended_registers status:intended_status value:final_state[@"regs"]]; - const bool registersEqual = intended_registers == registers; - const bool statusEqual = (intended_status.get() & flags_mask) == (status.get() & flags_mask); + const bool registersEqual = intended_registers == execution_support.registers; + const bool statusEqual = (intended_status.get() & flags_mask) == (execution_support.status.get() & flags_mask); if(assert) { XCTAssert( statusEqual, "Status doesn't match despite mask %04x — differs in %04x after %@; executing %@", flags_mask, - (intended_status.get() ^ status.get()) & flags_mask, + (intended_status.get() ^ execution_support.status.get()) & flags_mask, test[@"name"], [self toString:decoded.second offsetLength:4 immediateLength:4] ); From 6abc3b6cd777171f8a495c6a2202da95dbdddf74 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 7 Oct 2023 14:28:44 -0400 Subject: [PATCH 024/128] Collate all failures for printing at the end. --- OSBindings/Mac/Clock SignalTests/8088Tests.mm | 34 ++++++++++++------- 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/OSBindings/Mac/Clock SignalTests/8088Tests.mm b/OSBindings/Mac/Clock SignalTests/8088Tests.mm index b5383fa76..4e071a11f 100644 --- a/OSBindings/Mac/Clock SignalTests/8088Tests.mm +++ b/OSBindings/Mac/Clock SignalTests/8088Tests.mm @@ -191,6 +191,10 @@ struct ExecutionSupport { } }; +struct FailedExecution { + std::string file, test_name; + InstructionSet::x86::Instruction instruction; +}; } @@ -199,6 +203,7 @@ struct ExecutionSupport { @implementation i8088Tests { ExecutionSupport execution_support; + std::vector execution_failures; } - (NSArray *)testFiles { @@ -360,7 +365,7 @@ struct ExecutionSupport { status.set([value[@"flags"] intValue]); } -- (bool)applyExecutionTest:(NSDictionary *)test file:(NSString *)file metadata:(NSDictionary *)metadata assert:(BOOL)assert { +- (void)applyExecutionTest:(NSDictionary *)test file:(NSString *)file metadata:(NSDictionary *)metadata { InstructionSet::x86::Decoder decoder; const auto data = [self bytes:test[@"bytes"]]; const auto decoded = decoder.decode(data.data(), data.size()); @@ -414,7 +419,15 @@ struct ExecutionSupport { const bool registersEqual = intended_registers == execution_support.registers; const bool statusEqual = (intended_status.get() & flags_mask) == (execution_support.status.get() & flags_mask); - if(assert) { + if(!statusEqual || !registersEqual || !ramEqual) { + FailedExecution failure; + failure.instruction = decoded.second; +// failure.file = // TODO + failure.test_name = std::string([test[@"name"] UTF8String]); + execution_failures.push_back(std::move(failure)); + } + +/* if(assert) { XCTAssert( statusEqual, "Status doesn't match despite mask %04x — differs in %04x after %@; executing %@", @@ -438,7 +451,7 @@ struct ExecutionSupport { ); } - return statusEqual && registersEqual && ramEqual; + return statusEqual && registersEqual && ramEqual;*/ } - (void)printFailures:(NSArray *)failures { @@ -479,18 +492,15 @@ struct ExecutionSupport { NSDictionary *test_metadata = metadata[metadata_key]; for(NSDictionary *test in [self testsInFile:file]) { - // A single failure per instruction is fine. - if(![self applyExecutionTest:test file:file metadata:test_metadata assert:YES]) { - [failures addObject:file]; - - // Attempt a second decoding, to provide a debugger hook. - [self applyExecutionTest:test file:file metadata:test_metadata assert:NO]; - break; - } + [self applyExecutionTest:test file:file metadata:test_metadata]; } } - [self printFailures:failures]; + XCTAssertEqual(execution_failures.size(), 0); + + for(const auto &failure: execution_failures) { + NSLog(@"Failed %s", failure.test_name.c_str()); + } } @end From a5523c9febe68974f89b1e423ee6fbbd3e4c3872 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 7 Oct 2023 14:37:12 -0400 Subject: [PATCH 025/128] Fail at scale. 108,645 current failures (!) --- OSBindings/Mac/Clock SignalTests/8088Tests.mm | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/OSBindings/Mac/Clock SignalTests/8088Tests.mm b/OSBindings/Mac/Clock SignalTests/8088Tests.mm index 4e071a11f..3b4e5f3f2 100644 --- a/OSBindings/Mac/Clock SignalTests/8088Tests.mm +++ b/OSBindings/Mac/Clock SignalTests/8088Tests.mm @@ -209,7 +209,18 @@ struct FailedExecution { - (NSArray *)testFiles { NSString *path = [NSString stringWithUTF8String:TestSuiteHome]; NSSet *allowList = [NSSet setWithArray:@[ - @"D4.json.gz" + // ADC + @"10.json.gz", @"11.json.gz", @"12.json.gz", @"13.json.gz", @"14.json.gz", @"15.json.gz", + // TO add: 80/2, 81/2, 83/2 + + // ADD + @"00.json.gz", @"01.json.gz", @"02.json.gz", @"03.json.gz", @"04.json.gz", @"05.json.gz", + // TO add: 80/0, 81/0, 83/0 + + @"37.json.gz", // AAA + @"3F.json.gz", // AAS + @"D4.json.gz", // AAM + @"D5.json.gz", // AAD ]]; NSSet *ignoreList = nil; @@ -483,7 +494,6 @@ struct FailedExecution { - (void)testExecution { NSDictionary *metadata = [self metadata]; - NSMutableArray *failures = [[NSMutableArray alloc] init]; for(NSString *file in [self testFiles]) { // Determine what the metadata key. NSString *const name = [file lastPathComponent]; From a4b1d2b00afd0bd07f7bfd16644e89e1b72efc2d Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 8 Oct 2023 13:34:28 -0400 Subject: [PATCH 026/128] Float out data resolution. --- .../Implementation/PerformImplementation.hpp | 162 +++++++++--------- OSBindings/Mac/Clock SignalTests/8088Tests.mm | 22 ++- 2 files changed, 100 insertions(+), 84 deletions(-) diff --git a/InstructionSets/x86/Implementation/PerformImplementation.hpp b/InstructionSets/x86/Implementation/PerformImplementation.hpp index a5c427c22..ebabf7c1e 100644 --- a/InstructionSets/x86/Implementation/PerformImplementation.hpp +++ b/InstructionSets/x86/Implementation/PerformImplementation.hpp @@ -200,6 +200,88 @@ void add(IntT &destination, IntT source, Status &status) { } +template +typename DataSizeType::type *resolve(InstructionT &instruction, DataPointer source, RegistersT ®isters, MemoryT &memory) { + // Rules: + // + // * if this is a memory access, set target_address and break; + // * otherwise return the appropriate value. + uint32_t address; + switch(source.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) && data_size == DataSize::DWord) { return ®isters.eax(); } + else if constexpr (data_size == DataSize::Word) { return ®isters.ax(); } + else if constexpr (data_size == DataSize::Byte) { return ®isters.al(); } + else { return nullptr; } + case Source::eCX: + if constexpr (is_32bit(model) && data_size == DataSize::DWord) { return ®isters.ecx(); } + else if constexpr (data_size == DataSize::Word) { return ®isters.cx(); } + else if constexpr (data_size == DataSize::Byte) { return ®isters.cl(); } + else { return nullptr; } + case Source::eDX: + if constexpr (is_32bit(model) && data_size == DataSize::DWord) { return ®isters.edx(); } + else if constexpr (data_size == DataSize::Word) { return ®isters.dx(); } + else if constexpr (data_size == DataSize::Byte) { return ®isters.dl(); } + else if constexpr (data_size == DataSize::DWord) { return nullptr; } + case Source::eBX: + if constexpr (is_32bit(model) && data_size == DataSize::DWord) { return ®isters.ebx(); } + else if constexpr (data_size == DataSize::Word) { return ®isters.bx(); } + else if constexpr (data_size == DataSize::Byte) { return ®isters.bl(); } + else if constexpr (data_size == DataSize::DWord) { return nullptr; } + case Source::eSPorAH: + if constexpr (is_32bit(model) && data_size == DataSize::DWord) { return ®isters.esp(); } + else if constexpr (data_size == DataSize::Word) { return ®isters.sp(); } + else if constexpr (data_size == DataSize::Byte) { return ®isters.ah(); } + else { return nullptr; } + case Source::eBPorCH: + if constexpr (is_32bit(model) && data_size == DataSize::DWord) { return ®isters.ebp(); } + else if constexpr (data_size == DataSize::Word) { return ®isters.bp(); } + else if constexpr (data_size == DataSize::Byte) { return ®isters.ch(); } + else { return nullptr; } + case Source::eSIorDH: + if constexpr (is_32bit(model) && data_size == DataSize::DWord) { return ®isters.esi(); } + else if constexpr (data_size == DataSize::Word) { return ®isters.si(); } + else if constexpr (data_size == DataSize::Byte) { return ®isters.dh(); } + else { return nullptr; } + case Source::eDIorBH: + if constexpr (is_32bit(model) && data_size == DataSize::DWord) { return ®isters.edi(); } + else if constexpr (data_size == DataSize::Word) { return ®isters.di(); } + else if constexpr (data_size == DataSize::Byte) { return ®isters.bh(); } + else { return nullptr; } + + case Source::ES: if constexpr (data_size == DataSize::Word) return ®isters.es(); else return nullptr; + case Source::CS: if constexpr (data_size == DataSize::Word) return ®isters.cs(); else return nullptr; + case Source::SS: if constexpr (data_size == DataSize::Word) return ®isters.ss(); else return nullptr; + case Source::DS: if constexpr (data_size == DataSize::Word) return ®isters.ds(); else return nullptr; + + // 16-bit models don't have FS and GS. + case Source::FS: if constexpr (is_32bit(model) && data_size == DataSize::Word) return ®isters.fs(); else return nullptr; + case Source::GS: if constexpr (is_32bit(model) && data_size == DataSize::Word) return ®isters.gs(); else return nullptr; + + case Source::Immediate: // TODO (here the use of a pointer falls down?) + + case Source::None: return nullptr; + + case Source::Indirect: // TODO + + case Source::IndirectNoBase: // TODO + + case Source::DirectAddress: + address = instruction.offset(); + break; + } + + // If execution has reached here then a memory fetch is required. + // Do it and exit. + const Source segment = source.segment(instruction.segment_override()); + using IntT = typename DataSizeType::type; + return &memory.template access(segment, address); +}; + template < Model model, DataSize data_size, @@ -219,85 +301,9 @@ template < using IntT = typename DataSizeType::type; using AddressT = typename AddressT::type; - IntT zero = 0; - auto data = [&](DataPointer source) -> IntT& { - // Rules: - // - // * if this is a memory access, set target_address and break; - // * otherwise return the appropriate value. - AddressT address; - switch(source.source()) { - case Source::eAX: - if constexpr (is_32bit(model) && data_size == DataSize::DWord) { return registers.eax(); } - else if constexpr (data_size == DataSize::DWord) { return zero; } - else if constexpr (data_size == DataSize::Word) { return registers.ax(); } - else { return registers.al(); } - case Source::eCX: - if constexpr (is_32bit(model) && data_size == DataSize::DWord) { return registers.ecx(); } - else if constexpr (data_size == DataSize::DWord) { return zero; } - else if constexpr (data_size == DataSize::Word) { return registers.cx(); } - else { return registers.cl(); } - case Source::eDX: - if constexpr (is_32bit(model) && data_size == DataSize::DWord) { return registers.edx(); } - else if constexpr (data_size == DataSize::DWord) { return zero; } - else if constexpr (data_size == DataSize::Word) { return registers.dx(); } - else { return registers.dl(); } - case Source::eBX: - if constexpr (is_32bit(model) && data_size == DataSize::DWord) { return registers.ebx(); } - else if constexpr (data_size == DataSize::DWord) { return zero; } - else if constexpr (data_size == DataSize::Word) { return registers.bx(); } - else { return registers.bl(); } - case Source::eSPorAH: - if constexpr (is_32bit(model) && data_size == DataSize::DWord) { return registers.esp(); } - else if constexpr (data_size == DataSize::DWord) { return zero; } - else if constexpr (data_size == DataSize::Word) { return registers.sp(); } - else { return registers.ah(); } - case Source::eBPorCH: - if constexpr (is_32bit(model) && data_size == DataSize::DWord) { return registers.ebp(); } - else if constexpr (data_size == DataSize::DWord) { return zero; } - else if constexpr (data_size == DataSize::Word) { return registers.bp(); } - else { return registers.ch(); } - case Source::eSIorDH: - if constexpr (is_32bit(model) && data_size == DataSize::DWord) { return registers.esi(); } - else if constexpr (data_size == DataSize::DWord) { return zero; } - else if constexpr (data_size == DataSize::Word) { return registers.si(); } - else { return registers.dh(); } - case Source::eDIorBH: - if constexpr (is_32bit(model) && data_size == DataSize::DWord) { return registers.edi(); } - else if constexpr (data_size == DataSize::DWord) { return zero; } - else if constexpr (data_size == DataSize::Word) { return registers.di(); } - else { return registers.bh(); } - - // TODO: the below. - default: -// case Source::ES: return registers.es(); -// case Source::CS: return registers.cs(); -// case Source::SS: return registers.ss(); -// case Source::DS: return registers.ds(); -// case Source::FS: return registers.fs(); -// case Source::GS: return registers.gs(); - - case Source::Immediate: // TODO (here the use of a reference falls down?) - - case Source::None: return zero; - - case Source::Indirect: // TODO - case Source::IndirectNoBase: // TODO - - case Source::DirectAddress: - address = instruction.offset(); - break; - } - - // If execution has reached here then a memory fetch is required. - // Do it and exit. - const Source segment = source.segment(instruction.segment_override()); - return memory.template access(segment, address); - }; - // Establish source() and destination() shorthand to fetch data if necessary. - auto source = [&]() -> IntT& { return data(instruction.source()); }; - auto destination = [&]() -> IntT& { return data(instruction.destination()); }; + auto source = [&]() -> IntT& { return *resolve(instruction, instruction.source(), registers, memory); }; + auto destination = [&]() -> IntT& { return *resolve(instruction, instruction.destination(), registers, memory); }; // Guide to the below: // diff --git a/OSBindings/Mac/Clock SignalTests/8088Tests.mm b/OSBindings/Mac/Clock SignalTests/8088Tests.mm index 3b4e5f3f2..fc747e0c4 100644 --- a/OSBindings/Mac/Clock SignalTests/8088Tests.mm +++ b/OSBindings/Mac/Clock SignalTests/8088Tests.mm @@ -66,6 +66,16 @@ struct Registers { uint16_t es_, cs_, ds_, ss_; uint16_t ip_; + uint16_t &es() { return es_; } + uint16_t &cs() { return cs_; } + uint16_t &ds() { return ds_; } + uint16_t &ss() { return ss_; } + +// uint32_t zero_ = 0; +// template IntT &zero() { +// return static_cast(zero); +// } + bool operator ==(const Registers &rhs) const { return ax_.full == rhs.ax_.full && @@ -210,17 +220,17 @@ struct FailedExecution { NSString *path = [NSString stringWithUTF8String:TestSuiteHome]; NSSet *allowList = [NSSet setWithArray:@[ // ADC - @"10.json.gz", @"11.json.gz", @"12.json.gz", @"13.json.gz", @"14.json.gz", @"15.json.gz", +// @"10.json.gz", @"11.json.gz", @"12.json.gz", @"13.json.gz", @"14.json.gz", @"15.json.gz", // TO add: 80/2, 81/2, 83/2 // ADD - @"00.json.gz", @"01.json.gz", @"02.json.gz", @"03.json.gz", @"04.json.gz", @"05.json.gz", + @"00.json.gz", //@"01.json.gz", @"02.json.gz", @"03.json.gz", @"04.json.gz", @"05.json.gz", // TO add: 80/0, 81/0, 83/0 - @"37.json.gz", // AAA - @"3F.json.gz", // AAS - @"D4.json.gz", // AAM - @"D5.json.gz", // AAD +// @"37.json.gz", // AAA +// @"3F.json.gz", // AAS +// @"D4.json.gz", // AAM +// @"D5.json.gz", // AAD ]]; NSSet *ignoreList = nil; From dd3cc1f5103df7c3ce9aebc7010b7e533394898c Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 8 Oct 2023 13:39:46 -0400 Subject: [PATCH 027/128] Fix ADD and ADC sign flags. --- .../x86/Implementation/PerformImplementation.hpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/InstructionSets/x86/Implementation/PerformImplementation.hpp b/InstructionSets/x86/Implementation/PerformImplementation.hpp index ebabf7c1e..73f73097a 100644 --- a/InstructionSets/x86/Implementation/PerformImplementation.hpp +++ b/InstructionSets/x86/Implementation/PerformImplementation.hpp @@ -174,7 +174,8 @@ void adc(IntT &destination, IntT source, Status &status) { status.carry = Numeric::carried_out() - 1>(destination, source, result); status.auxiliary_carry = Numeric::carried_in<4>(destination, source, result); - status.sign = status.zero = status.parity = result; + status.sign = result & top_bit(); + status.zero = status.parity = result; status.overflow = overflow(destination, source, result); destination = result; @@ -192,7 +193,8 @@ void add(IntT &destination, IntT source, Status &status) { status.carry = Numeric::carried_out() - 1>(destination, source, result); status.auxiliary_carry = Numeric::carried_in<4>(destination, source, result); - status.sign = status.zero = status.parity = result; + status.sign = result & top_bit(); + status.zero = status.parity = result; status.overflow = overflow(destination, source, result); destination = result; From 6f768d9a3da8735b41f476f101a6b11ffcedf934 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 8 Oct 2023 13:47:43 -0400 Subject: [PATCH 028/128] Start climbing towards address resolution. --- .../Implementation/PerformImplementation.hpp | 26 ++++++++++++++----- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/InstructionSets/x86/Implementation/PerformImplementation.hpp b/InstructionSets/x86/Implementation/PerformImplementation.hpp index 73f73097a..e64042c4f 100644 --- a/InstructionSets/x86/Implementation/PerformImplementation.hpp +++ b/InstructionSets/x86/Implementation/PerformImplementation.hpp @@ -203,13 +203,13 @@ void add(IntT &destination, IntT source, Status &status) { } template -typename DataSizeType::type *resolve(InstructionT &instruction, DataPointer source, RegistersT ®isters, MemoryT &memory) { +typename DataSizeType::type *resolve(InstructionT &instruction, Source source, DataPointer pointer, RegistersT ®isters, MemoryT &memory) { // Rules: // // * if this is a memory access, set target_address and break; // * otherwise return the appropriate value. uint32_t address; - switch(source.source()) { + switch(source) { case Source::eAX: // Slightly contorted if chain here and below: // @@ -268,9 +268,21 @@ typename DataSizeType::type *resolve(InstructionT &instruction, DataP case Source::None: return nullptr; - case Source::Indirect: // TODO + case Source::Indirect: // TODO: non-word indexes and bases. + address = *resolve(instruction, pointer.index(), pointer, registers, memory); + if constexpr (is_32bit(model)) { + address <<= pointer.scale(); + } + address += instruction.offset() + *resolve(instruction, pointer.base(), pointer, registers, memory); + break; - case Source::IndirectNoBase: // TODO + case Source::IndirectNoBase: // TODO: non-word indexes and bases. + address = *resolve(instruction, pointer.index(), pointer, registers, memory); + if constexpr (is_32bit(model)) { + address <<= pointer.scale(); + } + address += instruction.offset(); + break; case Source::DirectAddress: address = instruction.offset(); @@ -279,7 +291,7 @@ typename DataSizeType::type *resolve(InstructionT &instruction, DataP // If execution has reached here then a memory fetch is required. // Do it and exit. - const Source segment = source.segment(instruction.segment_override()); + const Source segment = pointer.segment(instruction.segment_override()); using IntT = typename DataSizeType::type; return &memory.template access(segment, address); }; @@ -304,8 +316,8 @@ template < using AddressT = typename AddressT::type; // Establish source() and destination() shorthand to fetch data if necessary. - auto source = [&]() -> IntT& { return *resolve(instruction, instruction.source(), registers, memory); }; - auto destination = [&]() -> IntT& { return *resolve(instruction, instruction.destination(), registers, memory); }; + auto source = [&]() -> IntT& { return *resolve(instruction, instruction.source().template source(), instruction.source(), registers, memory); }; + auto destination = [&]() -> IntT& { return *resolve(instruction, instruction.destination().template source(), instruction.source(), registers, memory); }; // Guide to the below: // From 0d2af80f7f020e7dbe595965115527412c59f326 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 8 Oct 2023 13:50:36 -0400 Subject: [PATCH 029/128] Avoid access issues if there's no index. --- .../Implementation/PerformImplementation.hpp | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/InstructionSets/x86/Implementation/PerformImplementation.hpp b/InstructionSets/x86/Implementation/PerformImplementation.hpp index e64042c4f..ce0ae512d 100644 --- a/InstructionSets/x86/Implementation/PerformImplementation.hpp +++ b/InstructionSets/x86/Implementation/PerformImplementation.hpp @@ -203,7 +203,7 @@ void add(IntT &destination, IntT source, Status &status) { } template -typename DataSizeType::type *resolve(InstructionT &instruction, Source source, DataPointer pointer, RegistersT ®isters, MemoryT &memory) { +typename DataSizeType::type *resolve(InstructionT &instruction, Source source, DataPointer pointer, RegistersT ®isters, MemoryT &memory, typename DataSizeType::type *none = nullptr) { // Rules: // // * if this is a memory access, set target_address and break; @@ -266,23 +266,26 @@ typename DataSizeType::type *resolve(InstructionT &instruction, Sourc case Source::Immediate: // TODO (here the use of a pointer falls down?) - case Source::None: return nullptr; + case Source::None: return none; - case Source::Indirect: // TODO: non-word indexes and bases. - address = *resolve(instruction, pointer.index(), pointer, registers, memory); + // TODO: non-word indexes and bases in the next two cases. + case Source::Indirect: { + uint16_t zero = 0; + address = *resolve(instruction, pointer.index(), pointer, registers, memory, &zero); if constexpr (is_32bit(model)) { address <<= pointer.scale(); } address += instruction.offset() + *resolve(instruction, pointer.base(), pointer, registers, memory); - break; + } break; - case Source::IndirectNoBase: // TODO: non-word indexes and bases. - address = *resolve(instruction, pointer.index(), pointer, registers, memory); + case Source::IndirectNoBase: { + uint16_t zero = 0; + address = *resolve(instruction, pointer.index(), pointer, registers, memory, &zero); if constexpr (is_32bit(model)) { address <<= pointer.scale(); } address += instruction.offset(); - break; + } break; case Source::DirectAddress: address = instruction.offset(); From 6808f2c778fd5ed87c0bfd3f8ed17b4bf7ced7ce Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 8 Oct 2023 15:44:11 -0400 Subject: [PATCH 030/128] Attempt to catch illegal accesses ahead of time. --- OSBindings/Mac/Clock SignalTests/8088Tests.mm | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/OSBindings/Mac/Clock SignalTests/8088Tests.mm b/OSBindings/Mac/Clock SignalTests/8088Tests.mm index fc747e0c4..08c0681d9 100644 --- a/OSBindings/Mac/Clock SignalTests/8088Tests.mm +++ b/OSBindings/Mac/Clock SignalTests/8088Tests.mm @@ -95,6 +95,7 @@ struct Registers { }; struct Memory { enum class Tag { + Seeded, Accessed, FlagsL, FlagsH @@ -112,6 +113,11 @@ struct Memory { tags.clear(); } + void seed(uint32_t address, uint8_t value) { + memory[address] = value; + tags[address] = Tag::Seeded; + } + // 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([[maybe_unused]] InstructionSet::x86::Source segment, uint16_t address, Tag tag) { @@ -136,6 +142,9 @@ 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) { + if(tags.find(address) == tags.end()) { + printf("Access to uninitialised RAM area"); + } return access(segment, address, Tag::Accessed); } }; @@ -399,7 +408,7 @@ struct FailedExecution { NSDictionary *const initial_state = test[@"initial"]; InstructionSet::x86::Status initial_status; for(NSArray *ram in initial_state[@"ram"]) { - execution_support.memory.memory[[ram[0] intValue]] = [ram[1] intValue]; + execution_support.memory.seed([ram[0] intValue], [ram[1] intValue]); } [self populate:execution_support.registers status:initial_status value:initial_state[@"regs"]]; execution_support.status = initial_status; From dbfaef632a27fc7e5cfc10797e10a337b422b742 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 8 Oct 2023 15:59:30 -0400 Subject: [PATCH 031/128] Fix DataPointer reference. Down from 4521 to 1248 failures within 00.json.gz --- .../Implementation/PerformImplementation.hpp | 4 ++-- OSBindings/Mac/Clock SignalTests/8088Tests.mm | 18 +++++++++++++----- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/InstructionSets/x86/Implementation/PerformImplementation.hpp b/InstructionSets/x86/Implementation/PerformImplementation.hpp index ce0ae512d..eefce4e96 100644 --- a/InstructionSets/x86/Implementation/PerformImplementation.hpp +++ b/InstructionSets/x86/Implementation/PerformImplementation.hpp @@ -319,8 +319,8 @@ template < using AddressT = typename AddressT::type; // Establish source() and destination() shorthand to fetch data if necessary. - auto source = [&]() -> IntT& { return *resolve(instruction, instruction.source().template source(), instruction.source(), registers, memory); }; - auto destination = [&]() -> IntT& { return *resolve(instruction, instruction.destination().template source(), instruction.source(), registers, memory); }; + auto source = [&]() -> IntT& { return *resolve(instruction, instruction.source().template source(), instruction.source(), registers, memory); }; + auto destination = [&]() -> IntT& { return *resolve(instruction, instruction.destination().template source(), instruction.destination(), registers, memory); }; // Guide to the below: // diff --git a/OSBindings/Mac/Clock SignalTests/8088Tests.mm b/OSBindings/Mac/Clock SignalTests/8088Tests.mm index 08c0681d9..c47a28109 100644 --- a/OSBindings/Mac/Clock SignalTests/8088Tests.mm +++ b/OSBindings/Mac/Clock SignalTests/8088Tests.mm @@ -96,6 +96,7 @@ struct Registers { struct Memory { enum class Tag { Seeded, + AccessExpected, Accessed, FlagsL, FlagsH @@ -118,6 +119,10 @@ struct Memory { tags[address] = Tag::Seeded; } + void touch(uint32_t address) { + tags[address] = Tag::AccessExpected; + } + // 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([[maybe_unused]] InstructionSet::x86::Source segment, uint16_t address, Tag tag) { @@ -136,15 +141,15 @@ struct Memory { // 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) { + if(tags.find(address) == tags.end()) { +// printf("Access to unexpected RAM address"); + } tags[address] = tag; return *reinterpret_cast(&memory[address]); } // Entry point for the 8086; simply notes that memory was accessed. template IntT &access([[maybe_unused]] InstructionSet::x86::Source segment, uint32_t address) { - if(tags.find(address) == tags.end()) { - printf("Access to uninitialised RAM area"); - } return access(segment, address, Tag::Accessed); } }; @@ -403,13 +408,17 @@ struct FailedExecution { execution_support.clear(); const uint16_t flags_mask = metadata[@"flags-mask"] ? [metadata[@"flags-mask"] intValue] : 0xffff; + NSDictionary *const initial_state = test[@"initial"]; + NSDictionary *const final_state = test[@"final"]; // Apply initial state. - NSDictionary *const initial_state = test[@"initial"]; InstructionSet::x86::Status initial_status; for(NSArray *ram in initial_state[@"ram"]) { execution_support.memory.seed([ram[0] intValue], [ram[1] intValue]); } + for(NSArray *ram in final_state[@"ram"]) { + execution_support.memory.touch([ram[0] intValue]); + } [self populate:execution_support.registers status:initial_status value:initial_state[@"regs"]]; execution_support.status = initial_status; @@ -425,7 +434,6 @@ struct FailedExecution { ); // Compare final state. - NSDictionary *const final_state = test[@"final"]; Registers intended_registers; InstructionSet::x86::Status intended_status; From bd0b62232f5eb87a1d789d371acee3e8215a4249 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 8 Oct 2023 21:41:36 -0400 Subject: [PATCH 032/128] Consider that displacements may always be signed. Down to 1 failure. --- InstructionSets/x86/Decoder.cpp | 18 ++++----- .../Implementation/PerformImplementation.hpp | 38 +++++++++++++++++-- OSBindings/Mac/Clock SignalTests/8088Tests.mm | 6 +-- 3 files changed, 46 insertions(+), 16 deletions(-) diff --git a/InstructionSets/x86/Decoder.cpp b/InstructionSets/x86/Decoder.cpp index a8c3f92d4..ee4a79598 100644 --- a/InstructionSets/x86/Decoder.cpp +++ b/InstructionSets/x86/Decoder.cpp @@ -1018,21 +1018,21 @@ std::pair::InstructionT> Decoder::decode(con // TODO: whether the displacement is signed appears to depend on the opcode. // Find an appropriate table. - if(!sign_extend_displacement_) { - switch(displacement_size_) { - case DataSize::None: displacement_ = 0; break; - case DataSize::Byte: displacement_ = decltype(displacement_)(uint8_t(inward_data_)); break; - case DataSize::Word: displacement_ = decltype(displacement_)(uint16_t(inward_data_)); break; - case DataSize::DWord: displacement_ = decltype(displacement_)(uint32_t(inward_data_)); break; - } - } else { +// if(!sign_extend_displacement_) { +// switch(displacement_size_) { +// case DataSize::None: displacement_ = 0; break; +// case DataSize::Byte: displacement_ = decltype(displacement_)(uint8_t(inward_data_)); break; +// case DataSize::Word: displacement_ = decltype(displacement_)(uint16_t(inward_data_)); break; +// case DataSize::DWord: displacement_ = decltype(displacement_)(uint32_t(inward_data_)); break; +// } +// } else { switch(displacement_size_) { case DataSize::None: displacement_ = 0; break; case DataSize::Byte: displacement_ = int8_t(inward_data_); break; case DataSize::Word: displacement_ = int16_t(inward_data_); break; case DataSize::DWord: displacement_ = int32_t(inward_data_); break; } - } +// } inward_data_ >>= bit_size(displacement_size_); // Use inequality of sizes as a test for necessary sign extension. diff --git a/InstructionSets/x86/Implementation/PerformImplementation.hpp b/InstructionSets/x86/Implementation/PerformImplementation.hpp index eefce4e96..9940caa37 100644 --- a/InstructionSets/x86/Implementation/PerformImplementation.hpp +++ b/InstructionSets/x86/Implementation/PerformImplementation.hpp @@ -203,7 +203,16 @@ void add(IntT &destination, IntT source, Status &status) { } template -typename DataSizeType::type *resolve(InstructionT &instruction, Source source, DataPointer pointer, RegistersT ®isters, MemoryT &memory, typename DataSizeType::type *none = nullptr) { +typename DataSizeType::type * +resolve( + InstructionT &instruction, + Source source, + DataPointer pointer, + RegistersT ®isters, + MemoryT &memory, + typename DataSizeType::type *none = nullptr, + typename DataSizeType::type *immediate = nullptr +) { // Rules: // // * if this is a memory access, set target_address and break; @@ -264,7 +273,9 @@ typename DataSizeType::type *resolve(InstructionT &instruction, Sourc case Source::FS: if constexpr (is_32bit(model) && data_size == DataSize::Word) return ®isters.fs(); else return nullptr; case Source::GS: if constexpr (is_32bit(model) && data_size == DataSize::Word) return ®isters.gs(); else return nullptr; - case Source::Immediate: // TODO (here the use of a pointer falls down?) + case Source::Immediate: + *immediate = instruction.operand(); + return immediate; case Source::None: return none; @@ -319,8 +330,27 @@ template < using AddressT = typename AddressT::type; // Establish source() and destination() shorthand to fetch data if necessary. - auto source = [&]() -> IntT& { return *resolve(instruction, instruction.source().template source(), instruction.source(), registers, memory); }; - auto destination = [&]() -> IntT& { return *resolve(instruction, instruction.destination().template source(), instruction.destination(), registers, memory); }; + IntT immediate; + auto source = [&]() -> IntT& { + return *resolve( + instruction, + instruction.source().template source(), + instruction.source(), + registers, + memory, + nullptr, + &immediate); + }; + auto destination = [&]() -> IntT& { + return *resolve( + instruction, + instruction.destination().template source(), + instruction.destination(), + registers, + memory, + nullptr, + &immediate); + }; // Guide to the below: // diff --git a/OSBindings/Mac/Clock SignalTests/8088Tests.mm b/OSBindings/Mac/Clock SignalTests/8088Tests.mm index c47a28109..0b208bd55 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 { @@ -142,7 +142,7 @@ struct Memory { // to a selector, they're just at an absolute location. template IntT &access(uint32_t address, Tag tag) { if(tags.find(address) == tags.end()) { -// printf("Access to unexpected RAM address"); + printf("Access to unexpected RAM address"); } tags[address] = tag; return *reinterpret_cast(&memory[address]); @@ -234,7 +234,7 @@ struct FailedExecution { NSString *path = [NSString stringWithUTF8String:TestSuiteHome]; NSSet *allowList = [NSSet setWithArray:@[ // ADC -// @"10.json.gz", @"11.json.gz", @"12.json.gz", @"13.json.gz", @"14.json.gz", @"15.json.gz", + @"10.json.gz", @"11.json.gz", @"12.json.gz", @"13.json.gz", @"14.json.gz", @"15.json.gz", // TO add: 80/2, 81/2, 83/2 // ADD From e3b18708c7fae6f656c3363547571fd25aac6ea4 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 8 Oct 2023 22:11:05 -0400 Subject: [PATCH 033/128] Handle segment-boundary word accesses. With all ADDs and ADCs enabled, no remaining failures. --- .../Implementation/PerformImplementation.hpp | 4 +- OSBindings/Mac/Clock SignalTests/8088Tests.mm | 49 ++++++++++++++++--- 2 files changed, 42 insertions(+), 11 deletions(-) diff --git a/InstructionSets/x86/Implementation/PerformImplementation.hpp b/InstructionSets/x86/Implementation/PerformImplementation.hpp index 9940caa37..13b2a89e1 100644 --- a/InstructionSets/x86/Implementation/PerformImplementation.hpp +++ b/InstructionSets/x86/Implementation/PerformImplementation.hpp @@ -371,9 +371,7 @@ template < } // Write to memory if required to complete this operation. -// if(original_data != fetched_data) { - // TODO. -// } + memory.template write_back(); } template < diff --git a/OSBindings/Mac/Clock SignalTests/8088Tests.mm b/OSBindings/Mac/Clock SignalTests/8088Tests.mm index 0b208bd55..458854553 100644 --- a/OSBindings/Mac/Clock SignalTests/8088Tests.mm +++ b/OSBindings/Mac/Clock SignalTests/8088Tests.mm @@ -123,9 +123,7 @@ struct Memory { tags[address] = Tag::AccessExpected; } - // 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([[maybe_unused]] InstructionSet::x86::Source segment, uint16_t address, Tag tag) { + uint32_t segment_base(InstructionSet::x86::Source segment) { uint32_t physical_address; using Source = InstructionSet::x86::Source; switch(segment) { @@ -134,7 +132,13 @@ struct Memory { case Source::CS: physical_address = registers_.cs_; break; case Source::SS: physical_address = registers_.ss_; break; } - physical_address = ((physical_address << 4) + address) & 0xf'ffff; + return physical_address << 4; + } + + // Entry point used by the flow controller so that it can mark up locations at which the flags were written, + // so that defined-flag-only masks can be applied while verifying RAM contents. + template IntT &access(InstructionSet::x86::Source segment, uint16_t address, Tag tag) { + const uint32_t physical_address = (segment_base(segment) + address) & 0xf'ffff; return access(physical_address, tag); } @@ -150,8 +154,32 @@ 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) { + 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_ = (segment_base(segment) + address) & 0xf'ffff; + write_back_value_ = memory[write_back_address_] | (memory[write_back_address_ - 65535] << 8); + return write_back_value_; + } + } return access(segment, address, Tag::Accessed); } + + template + void write_back() { + if constexpr (std::is_same_v) { + if(write_back_address_) { + memory[write_back_address_] = write_back_value_ & 0xff; + memory[write_back_address_ - 65535] = write_back_value_ >> 8; + write_back_address_ = 0; + } + } + } + + static constexpr uint32_t NoWriteBack = 0; // Zero can never be an address that triggers write back, conveniently. + uint32_t write_back_address_ = NoWriteBack; + uint16_t write_back_value_; }; struct IO { }; @@ -235,11 +263,11 @@ struct FailedExecution { NSSet *allowList = [NSSet setWithArray:@[ // ADC @"10.json.gz", @"11.json.gz", @"12.json.gz", @"13.json.gz", @"14.json.gz", @"15.json.gz", - // TO add: 80/2, 81/2, 83/2 + @"80.2.json.gz", @"81.2.json.gz", @"83.2.json.gz", // ADD - @"00.json.gz", //@"01.json.gz", @"02.json.gz", @"03.json.gz", @"04.json.gz", @"05.json.gz", - // TO add: 80/0, 81/0, 83/0 + @"00.json.gz", @"01.json.gz", @"02.json.gz", @"03.json.gz", @"04.json.gz", @"05.json.gz", + @"80.0.json.gz", @"81.0.json.gz", @"83.0.json.gz", // @"37.json.gz", // AAA // @"3F.json.gz", // AAS @@ -522,12 +550,17 @@ struct FailedExecution { NSDictionary *metadata = [self metadata]; for(NSString *file in [self testFiles]) { - // Determine what the metadata key. + // Determine the metadata key. NSString *const name = [file lastPathComponent]; NSRange first_dot = [name rangeOfString:@"."]; NSString *metadata_key = [name substringToIndex:first_dot.location]; + // Grab the metadata. If it wants a reg field, inspect a little further. NSDictionary *test_metadata = metadata[metadata_key]; + if(test_metadata[@"reg"]) { + test_metadata = test_metadata[@"reg"][[NSString stringWithFormat:@"%c", [name characterAtIndex:first_dot.location+1]]]; + } + for(NSDictionary *test in [self testsInFile:file]) { [self applyExecutionTest:test file:file metadata:test_metadata]; } From 0a0d53103deba2cd608f0fd59e0a79d7646bac6a Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 8 Oct 2023 22:12:22 -0400 Subject: [PATCH 034/128] Enable tests for all implemented operations. Only the various AAM 00hs are failing, which I've yet to understand. --- OSBindings/Mac/Clock SignalTests/8088Tests.mm | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/OSBindings/Mac/Clock SignalTests/8088Tests.mm b/OSBindings/Mac/Clock SignalTests/8088Tests.mm index 458854553..d2dc793d7 100644 --- a/OSBindings/Mac/Clock SignalTests/8088Tests.mm +++ b/OSBindings/Mac/Clock SignalTests/8088Tests.mm @@ -269,10 +269,10 @@ struct FailedExecution { @"00.json.gz", @"01.json.gz", @"02.json.gz", @"03.json.gz", @"04.json.gz", @"05.json.gz", @"80.0.json.gz", @"81.0.json.gz", @"83.0.json.gz", -// @"37.json.gz", // AAA -// @"3F.json.gz", // AAS -// @"D4.json.gz", // AAM -// @"D5.json.gz", // AAD + @"37.json.gz", // AAA + @"3F.json.gz", // AAS + @"D4.json.gz", // AAM + @"D5.json.gz", // AAD ]]; NSSet *ignoreList = nil; From f618ca604611be96aab64b6c04641363b337ed50 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 8 Oct 2023 22:18:40 -0400 Subject: [PATCH 035/128] Implement, test AND. --- .../Implementation/PerformImplementation.hpp | 19 +++++++++++++++++++ OSBindings/Mac/Clock SignalTests/8088Tests.mm | 4 ++++ 2 files changed, 23 insertions(+) diff --git a/InstructionSets/x86/Implementation/PerformImplementation.hpp b/InstructionSets/x86/Implementation/PerformImplementation.hpp index 13b2a89e1..571ee440d 100644 --- a/InstructionSets/x86/Implementation/PerformImplementation.hpp +++ b/InstructionSets/x86/Implementation/PerformImplementation.hpp @@ -200,6 +200,23 @@ void add(IntT &destination, IntT source, Status &status) { destination = result; } +template +void and_(IntT &destination, IntT source, Status &status) { + /* + DEST ← DEST AND SRC; + */ + /* + The OF and CF flags are cleared; the SF, ZF, and PF flags are set according to the result. + The state of the AF flag is undefined. + */ + destination &= source; + + status.overflow = 0; + status.carry = 0; + status.sign = destination & top_bit(); + status.zero = status.parity = destination; +} + } template @@ -368,6 +385,8 @@ template < case Operation::ADC: Primitive::adc(destination(), source(), status); break; case Operation::ADD: Primitive::add(destination(), source(), status); break; + + case Operation::AND: Primitive::and_(destination(), source(), status); break; } // Write to memory if required to complete this operation. diff --git a/OSBindings/Mac/Clock SignalTests/8088Tests.mm b/OSBindings/Mac/Clock SignalTests/8088Tests.mm index d2dc793d7..a0227ed15 100644 --- a/OSBindings/Mac/Clock SignalTests/8088Tests.mm +++ b/OSBindings/Mac/Clock SignalTests/8088Tests.mm @@ -269,6 +269,10 @@ struct FailedExecution { @"00.json.gz", @"01.json.gz", @"02.json.gz", @"03.json.gz", @"04.json.gz", @"05.json.gz", @"80.0.json.gz", @"81.0.json.gz", @"83.0.json.gz", + // AND + @"20.json.gz", @"21.json.gz", @"22.json.gz", @"23.json.gz", @"24.json.gz", @"25.json.gz", + @"80.4.json.gz", @"81.4.json.gz", @"83.4.json.gz", + @"37.json.gz", // AAA @"3F.json.gz", // AAS @"D4.json.gz", // AAM From 4f14210ee07da70e765d14849e14bb8c09a3e302 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 8 Oct 2023 22:27:01 -0400 Subject: [PATCH 036/128] Remove ideas discarded. --- .../Implementation/PerformImplementation.hpp | 1 - InstructionSets/x86/Perform.hpp | 24 ------------------- 2 files changed, 25 deletions(-) diff --git a/InstructionSets/x86/Implementation/PerformImplementation.hpp b/InstructionSets/x86/Implementation/PerformImplementation.hpp index 571ee440d..012ad3e80 100644 --- a/InstructionSets/x86/Implementation/PerformImplementation.hpp +++ b/InstructionSets/x86/Implementation/PerformImplementation.hpp @@ -427,7 +427,6 @@ template < } } - } #endif /* PerformImplementation_h */ diff --git a/InstructionSets/x86/Perform.hpp b/InstructionSets/x86/Perform.hpp index da77b89b0..02aeecde3 100644 --- a/InstructionSets/x86/Perform.hpp +++ b/InstructionSets/x86/Perform.hpp @@ -35,30 +35,6 @@ template < IOT &io ); -/*template < - Model model, - Operation operation, - DataSize data_size, - typename FlowControllerT -> void perform( - CPU::RegisterPair32 &destination, - CPU::RegisterPair32 &source, - Status &status, - FlowControllerT &flow_controller -); - -template < - Model model, - Operation operation, - DataSize data_size, - typename FlowControllerT -> void perform( - CPU::RegisterPair16 &destination, - CPU::RegisterPair16 &source, - Status &status, - FlowControllerT &flow_controller -);*/ - } #include "Implementation/PerformImplementation.hpp" From 5a77f0c93cf8c054750342e0c863968c1ce2eda8 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 9 Oct 2023 11:46:59 -0400 Subject: [PATCH 037/128] Implement `CALL`. --- InstructionSets/x86/Decoder.cpp | 1 - .../Implementation/PerformImplementation.hpp | 294 +++++++++++------- OSBindings/Mac/Clock SignalTests/8088Tests.mm | 18 ++ 3 files changed, 206 insertions(+), 107 deletions(-) diff --git a/InstructionSets/x86/Decoder.cpp b/InstructionSets/x86/Decoder.cpp index ee4a79598..0101ac84d 100644 --- a/InstructionSets/x86/Decoder.cpp +++ b/InstructionSets/x86/Decoder.cpp @@ -885,7 +885,6 @@ std::pair::InstructionT> Decoder::decode(con case 4: SetOperation(Operation::JMPabs); break; case 5: SetOperation(Operation::JMPfar); break; } - // TODO: CALLfar and JMPfar aren't correct above; find out what is. break; case ModRegRMFormat::MemRegSingleOperand: diff --git a/InstructionSets/x86/Implementation/PerformImplementation.hpp b/InstructionSets/x86/Implementation/PerformImplementation.hpp index 012ad3e80..53ceff011 100644 --- a/InstructionSets/x86/Implementation/PerformImplementation.hpp +++ b/InstructionSets/x86/Implementation/PerformImplementation.hpp @@ -15,6 +15,144 @@ namespace InstructionSet::x86 { +template +IntT *resolve( + InstructionT &instruction, + Source source, + DataPointer pointer, + RegistersT ®isters, + MemoryT &memory, + IntT *none = nullptr, + IntT *immediate = nullptr +); + +template +uint32_t address( + InstructionT &instruction, + DataPointer pointer, + RegistersT ®isters, + MemoryT &memory +) { + // TODO: non-word indexes and bases. + uint32_t address; + switch(source) { + default: return 0; + case Source::Indirect: { + uint16_t zero = 0; + address = *resolve(instruction, pointer.index(), pointer, registers, memory, &zero); + if constexpr (is_32bit(model)) { + address <<= pointer.scale(); + } + address += instruction.offset() + *resolve(instruction, pointer.base(), pointer, registers, memory); + } break; + + case Source::IndirectNoBase: { + uint16_t zero = 0; + address = *resolve(instruction, pointer.index(), pointer, registers, memory, &zero); + if constexpr (is_32bit(model)) { + address <<= pointer.scale(); + } + address += instruction.offset(); + } break; + + case Source::DirectAddress: return instruction.offset(); + } + return address; +} + +template +IntT *resolve( + InstructionT &instruction, + Source source, + DataPointer pointer, + RegistersT ®isters, + MemoryT &memory, + IntT *none, + IntT *immediate +) { + // Rules: + // + // * if this is a memory access, set target_address and break; + // * otherwise return the appropriate value. + uint32_t target_address; + switch(source) { + case Source::eAX: + // Slightly contorted if chain here and below: + // + // (i) does the `constexpr` version of a `switch`; and + // (i) ensures .eax() etc aren't called on @c registers for 16-bit processors, so they need not implement 32-bit storage. + if constexpr (is_32bit(model) && std::is_same_v) { return ®isters.eax(); } + else if constexpr (std::is_same_v) { return ®isters.ax(); } + else if constexpr (std::is_same_v) { return ®isters.al(); } + else { return nullptr; } + case Source::eCX: + if constexpr (is_32bit(model) && std::is_same_v) { return ®isters.ecx(); } + else if constexpr (std::is_same_v) { return ®isters.cx(); } + else if constexpr (std::is_same_v) { return ®isters.cl(); } + else { return nullptr; } + case Source::eDX: + if constexpr (is_32bit(model) && std::is_same_v) { return ®isters.edx(); } + else if constexpr (std::is_same_v) { return ®isters.dx(); } + else if constexpr (std::is_same_v) { return ®isters.dl(); } + else if constexpr (std::is_same_v) { return nullptr; } + case Source::eBX: + if constexpr (is_32bit(model) && std::is_same_v) { return ®isters.ebx(); } + else if constexpr (std::is_same_v) { return ®isters.bx(); } + else if constexpr (std::is_same_v) { return ®isters.bl(); } + else if constexpr (std::is_same_v) { return nullptr; } + case Source::eSPorAH: + if constexpr (is_32bit(model) && std::is_same_v) { return ®isters.esp(); } + else if constexpr (std::is_same_v) { return ®isters.sp(); } + else if constexpr (std::is_same_v) { return ®isters.ah(); } + else { return nullptr; } + case Source::eBPorCH: + if constexpr (is_32bit(model) && std::is_same_v) { return ®isters.ebp(); } + else if constexpr (std::is_same_v) { return ®isters.bp(); } + else if constexpr (std::is_same_v) { return ®isters.ch(); } + else { return nullptr; } + case Source::eSIorDH: + if constexpr (is_32bit(model) && std::is_same_v) { return ®isters.esi(); } + else if constexpr (std::is_same_v) { return ®isters.si(); } + else if constexpr (std::is_same_v) { return ®isters.dh(); } + else { return nullptr; } + case Source::eDIorBH: + if constexpr (is_32bit(model) && std::is_same_v) { return ®isters.edi(); } + else if constexpr (std::is_same_v) { return ®isters.di(); } + else if constexpr (std::is_same_v) { return ®isters.bh(); } + else { return nullptr; } + + case Source::ES: if constexpr (std::is_same_v) return ®isters.es(); else return nullptr; + case Source::CS: if constexpr (std::is_same_v) return ®isters.cs(); else return nullptr; + case Source::SS: if constexpr (std::is_same_v) return ®isters.ss(); else return nullptr; + case Source::DS: if constexpr (std::is_same_v) return ®isters.ds(); else return nullptr; + + // 16-bit models don't have FS and GS. + case Source::FS: if constexpr (is_32bit(model) && std::is_same_v) return ®isters.fs(); else return nullptr; + case Source::GS: if constexpr (is_32bit(model) && std::is_same_v) return ®isters.gs(); else return nullptr; + + case Source::Immediate: + *immediate = instruction.operand(); + return immediate; + + case Source::None: return none; + + case Source::Indirect: + target_address = address(instruction, pointer, registers, memory); + break; + case Source::IndirectNoBase: + target_address = address(instruction, pointer, registers, memory); + break; + case Source::DirectAddress: + target_address = address(instruction, pointer, registers, memory); + break; + } + + // If execution has reached here then a memory fetch is required. + // Do it and exit. + const Source segment = pointer.segment(instruction.segment_override()); + return &memory.template access(segment, target_address); +}; + namespace Primitive { // @@ -217,115 +355,49 @@ void and_(IntT &destination, IntT source, Status &status) { status.zero = status.parity = destination; } +template +inline void call_relative(IntT offset, RegistersT ®isters, FlowControllerT &flow_controller) { + flow_controller.call(registers.ip() + offset); } -template -typename DataSizeType::type * -resolve( - InstructionT &instruction, - Source source, - DataPointer pointer, +template +inline void call_absolute(IntT target, FlowControllerT &flow_controller) { + flow_controller.call(target); +} + +template +inline void call_far(InstructionT &instruction, + FlowControllerT &flow_controller, RegistersT ®isters, - MemoryT &memory, - typename DataSizeType::type *none = nullptr, - typename DataSizeType::type *immediate = nullptr -) { - // Rules: - // - // * if this is a memory access, set target_address and break; - // * otherwise return the appropriate value. - uint32_t address; - 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) && data_size == DataSize::DWord) { return ®isters.eax(); } - else if constexpr (data_size == DataSize::Word) { return ®isters.ax(); } - else if constexpr (data_size == DataSize::Byte) { return ®isters.al(); } - else { return nullptr; } - case Source::eCX: - if constexpr (is_32bit(model) && data_size == DataSize::DWord) { return ®isters.ecx(); } - else if constexpr (data_size == DataSize::Word) { return ®isters.cx(); } - else if constexpr (data_size == DataSize::Byte) { return ®isters.cl(); } - else { return nullptr; } - case Source::eDX: - if constexpr (is_32bit(model) && data_size == DataSize::DWord) { return ®isters.edx(); } - else if constexpr (data_size == DataSize::Word) { return ®isters.dx(); } - else if constexpr (data_size == DataSize::Byte) { return ®isters.dl(); } - else if constexpr (data_size == DataSize::DWord) { return nullptr; } - case Source::eBX: - if constexpr (is_32bit(model) && data_size == DataSize::DWord) { return ®isters.ebx(); } - else if constexpr (data_size == DataSize::Word) { return ®isters.bx(); } - else if constexpr (data_size == DataSize::Byte) { return ®isters.bl(); } - else if constexpr (data_size == DataSize::DWord) { return nullptr; } - case Source::eSPorAH: - if constexpr (is_32bit(model) && data_size == DataSize::DWord) { return ®isters.esp(); } - else if constexpr (data_size == DataSize::Word) { return ®isters.sp(); } - else if constexpr (data_size == DataSize::Byte) { return ®isters.ah(); } - else { return nullptr; } - case Source::eBPorCH: - if constexpr (is_32bit(model) && data_size == DataSize::DWord) { return ®isters.ebp(); } - else if constexpr (data_size == DataSize::Word) { return ®isters.bp(); } - else if constexpr (data_size == DataSize::Byte) { return ®isters.ch(); } - else { return nullptr; } - case Source::eSIorDH: - if constexpr (is_32bit(model) && data_size == DataSize::DWord) { return ®isters.esi(); } - else if constexpr (data_size == DataSize::Word) { return ®isters.si(); } - else if constexpr (data_size == DataSize::Byte) { return ®isters.dh(); } - else { return nullptr; } - case Source::eDIorBH: - if constexpr (is_32bit(model) && data_size == DataSize::DWord) { return ®isters.edi(); } - else if constexpr (data_size == DataSize::Word) { return ®isters.di(); } - else if constexpr (data_size == DataSize::Byte) { return ®isters.bh(); } - else { return nullptr; } + MemoryT &memory) { - case Source::ES: if constexpr (data_size == DataSize::Word) return ®isters.es(); else return nullptr; - case Source::CS: if constexpr (data_size == DataSize::Word) return ®isters.cs(); else return nullptr; - case Source::SS: if constexpr (data_size == DataSize::Word) return ®isters.ss(); else return nullptr; - case Source::DS: if constexpr (data_size == DataSize::Word) return ®isters.ds(); else return nullptr; - - // 16-bit models don't have FS and GS. - case Source::FS: if constexpr (is_32bit(model) && data_size == DataSize::Word) return ®isters.fs(); else return nullptr; - case Source::GS: if constexpr (is_32bit(model) && data_size == DataSize::Word) return ®isters.gs(); else return nullptr; - - case Source::Immediate: - *immediate = instruction.operand(); - return immediate; - - case Source::None: return none; - - // TODO: non-word indexes and bases in the next two cases. - case Source::Indirect: { - uint16_t zero = 0; - address = *resolve(instruction, pointer.index(), pointer, registers, memory, &zero); - if constexpr (is_32bit(model)) { - address <<= pointer.scale(); - } - address += instruction.offset() + *resolve(instruction, pointer.base(), pointer, registers, memory); - } break; - - case Source::IndirectNoBase: { - uint16_t zero = 0; - address = *resolve(instruction, pointer.index(), pointer, registers, memory, &zero); - if constexpr (is_32bit(model)) { - address <<= pointer.scale(); - } - address += instruction.offset(); - } break; + // TODO: eliminate 16-bit assumption below. + uint16_t source_address = 0; + auto pointer = instruction.destination(); + switch(pointer.template source()) { + default: + case Source::Immediate: flow_controller.call(instruction.segment(), instruction.offset()); return; + case Source::Indirect: + source_address = address(instruction, pointer, registers, memory); + break; + case Source::IndirectNoBase: + source_address = address(instruction, pointer, registers, memory); + break; case Source::DirectAddress: - address = instruction.offset(); + source_address = address(instruction, pointer, registers, memory); break; } - // If execution has reached here then a memory fetch is required. - // Do it and exit. - const Source segment = pointer.segment(instruction.segment_override()); - using IntT = typename DataSizeType::type; - return &memory.template access(segment, address); -}; + const Source source_segment = pointer.segment(instruction.segment_override()); + + const uint16_t offset = memory.template access(source_segment, source_address); + source_address += 2; + const uint16_t segment = memory.template access(source_segment, source_address); + flow_controller.call(segment, offset); +} + +} template < Model model, @@ -338,9 +410,9 @@ template < > void perform( const InstructionT &instruction, Status &status, - [[maybe_unused]] FlowControllerT &flow_controller, + FlowControllerT &flow_controller, RegistersT ®isters, - [[maybe_unused]] MemoryT &memory, + MemoryT &memory, [[maybe_unused]] IOT &io ) { using IntT = typename DataSizeType::type; @@ -349,7 +421,7 @@ template < // Establish source() and destination() shorthand to fetch data if necessary. IntT immediate; auto source = [&]() -> IntT& { - return *resolve( + return *resolve( instruction, instruction.source().template source(), instruction.source(), @@ -359,7 +431,7 @@ template < &immediate); }; auto destination = [&]() -> IntT& { - return *resolve( + return *resolve( instruction, instruction.destination().template source(), instruction.destination(), @@ -375,8 +447,8 @@ template < // * return directly if there is definitely no possible write back to RAM; // * otherwise use the source() and destination() lambdas, and break in order to allow a writeback if necessary. switch(instruction.operation) { - default: return; - //assert(false); + default: + assert(false); case Operation::AAA: Primitive::aaa(registers.axp(), status); return; case Operation::AAD: Primitive::aad(registers.axp(), instruction.operand(), status); return; @@ -387,6 +459,16 @@ template < case Operation::ADD: Primitive::add(destination(), source(), status); break; case Operation::AND: Primitive::and_(destination(), source(), status); break; + + case Operation::CALLrel: + Primitive::call_relative(instruction.displacement(), registers, flow_controller); + return; + case Operation::CALLabs: + Primitive::call_absolute(destination(), flow_controller); + return; + case Operation::CALLfar: + Primitive::call_far(instruction, flow_controller, registers, memory); + return; } // Write to memory if required to complete this operation. diff --git a/OSBindings/Mac/Clock SignalTests/8088Tests.mm b/OSBindings/Mac/Clock SignalTests/8088Tests.mm index a0227ed15..9a412f39c 100644 --- a/OSBindings/Mac/Clock SignalTests/8088Tests.mm +++ b/OSBindings/Mac/Clock SignalTests/8088Tests.mm @@ -64,7 +64,9 @@ struct Registers { uint16_t &di() { return di_; } uint16_t es_, cs_, ds_, ss_; + uint16_t ip_; + uint16_t ip() { return ip_; } uint16_t &es() { return es_; } uint16_t &cs() { return cs_; } @@ -206,6 +208,18 @@ class FlowController { registers_.ip_ = new_ip; } + void call(uint16_t address) { + push(registers_.ip_); + registers_.ip_ = address; + } + + void call(uint16_t segment, uint16_t offset) { + push(registers_.cs_); + push(registers_.ip_); + registers_.cs_ = segment; + registers_.ip_ = offset; + } + private: Memory &memory_; Registers ®isters_; @@ -273,6 +287,10 @@ struct FailedExecution { @"20.json.gz", @"21.json.gz", @"22.json.gz", @"23.json.gz", @"24.json.gz", @"25.json.gz", @"80.4.json.gz", @"81.4.json.gz", @"83.4.json.gz", + // CALL + @"E8.json.gz", @"FF.2.json.gz", + @"9A.json.gz", @"FF.3.json.gz", + @"37.json.gz", // AAA @"3F.json.gz", // AAS @"D4.json.gz", // AAM From 769aed10ea5d7afa3c1310cf1840abdeba8871ec Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 9 Oct 2023 11:49:38 -0400 Subject: [PATCH 038/128] Reduce repetition. --- .../Implementation/PerformImplementation.hpp | 38 ++++++++----------- 1 file changed, 15 insertions(+), 23 deletions(-) diff --git a/InstructionSets/x86/Implementation/PerformImplementation.hpp b/InstructionSets/x86/Implementation/PerformImplementation.hpp index 53ceff011..f3ce62489 100644 --- a/InstructionSets/x86/Implementation/PerformImplementation.hpp +++ b/InstructionSets/x86/Implementation/PerformImplementation.hpp @@ -34,30 +34,22 @@ uint32_t address( MemoryT &memory ) { // TODO: non-word indexes and bases. - uint32_t address; - switch(source) { - default: return 0; - case Source::Indirect: { - uint16_t zero = 0; - address = *resolve(instruction, pointer.index(), pointer, registers, memory, &zero); - if constexpr (is_32bit(model)) { - address <<= pointer.scale(); - } - address += instruction.offset() + *resolve(instruction, pointer.base(), pointer, registers, memory); - } break; - - case Source::IndirectNoBase: { - uint16_t zero = 0; - address = *resolve(instruction, pointer.index(), pointer, registers, memory, &zero); - if constexpr (is_32bit(model)) { - address <<= pointer.scale(); - } - address += instruction.offset(); - } break; - - case Source::DirectAddress: return instruction.offset(); + if constexpr (source == Source::DirectAddress) { + return instruction.offset(); } - return address; + + uint32_t address; + uint16_t zero = 0; + address = *resolve(instruction, pointer.index(), pointer, registers, memory, &zero); + if constexpr (is_32bit(model)) { + address <<= pointer.scale(); + } + address += instruction.offset(); + + if constexpr (source == Source::IndirectNoBase) { + return address; + } + return address + *resolve(instruction, pointer.base(), pointer, registers, memory); } template From 59521f9d380ae39293e7113cec03f216419f2943 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 9 Oct 2023 11:59:38 -0400 Subject: [PATCH 039/128] Implement CBW, CLC, CLD, CLI, CMC. --- .../Implementation/PerformImplementation.hpp | 24 +++++++++++++++---- OSBindings/Mac/Clock SignalTests/8088Tests.mm | 5 ++++ 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/InstructionSets/x86/Implementation/PerformImplementation.hpp b/InstructionSets/x86/Implementation/PerformImplementation.hpp index f3ce62489..ba665a3bf 100644 --- a/InstructionSets/x86/Implementation/PerformImplementation.hpp +++ b/InstructionSets/x86/Implementation/PerformImplementation.hpp @@ -358,7 +358,7 @@ inline void call_absolute(IntT target, FlowControllerT &flow_controller) { } template -inline void call_far(InstructionT &instruction, +void call_far(InstructionT &instruction, FlowControllerT &flow_controller, RegistersT ®isters, MemoryT &memory) { @@ -389,6 +389,15 @@ inline void call_far(InstructionT &instruction, flow_controller.call(segment, offset); } +inline void cbw(CPU::RegisterPair16 &ax) { + ax.halves.high = (ax.halves.low & 0x80) ? 0xff : 0x00; +} + +inline void clc(Status &status) { status.carry = 0; } +inline void cld(Status &status) { status.direction = 0; } +inline void cli(Status &status) { status.interrupt = 0; } // TODO: quite a bit more in protected mode. +inline void cmc(Status &status) { status.carry = !status.carry; } + } template < @@ -447,10 +456,9 @@ template < case Operation::AAM: Primitive::aam(registers.axp(), instruction.operand(), status, flow_controller); return; case Operation::AAS: Primitive::aas(registers.axp(), status); return; - case Operation::ADC: Primitive::adc(destination(), source(), status); break; - case Operation::ADD: Primitive::add(destination(), source(), status); break; - - case Operation::AND: Primitive::and_(destination(), source(), status); break; + case Operation::ADC: Primitive::adc(destination(), source(), status); break; + case Operation::ADD: Primitive::add(destination(), source(), status); break; + case Operation::AND: Primitive::and_(destination(), source(), status); break; case Operation::CALLrel: Primitive::call_relative(instruction.displacement(), registers, flow_controller); @@ -461,6 +469,12 @@ template < case Operation::CALLfar: Primitive::call_far(instruction, flow_controller, registers, memory); return; + + case Operation::CBW: Primitive::cbw(registers.axp()); return; + case Operation::CLC: Primitive::clc(status); return; + case Operation::CLD: Primitive::cld(status); return; + case Operation::CLI: Primitive::cli(status); return; + case Operation::CMC: Primitive::cmc(status); return; } // Write to memory if required to complete this operation. diff --git a/OSBindings/Mac/Clock SignalTests/8088Tests.mm b/OSBindings/Mac/Clock SignalTests/8088Tests.mm index 9a412f39c..ef484181d 100644 --- a/OSBindings/Mac/Clock SignalTests/8088Tests.mm +++ b/OSBindings/Mac/Clock SignalTests/8088Tests.mm @@ -295,6 +295,11 @@ struct FailedExecution { @"3F.json.gz", // AAS @"D4.json.gz", // AAM @"D5.json.gz", // AAD + @"98.json.gz", // CBW + @"F8.json.gz", // CLC + @"FC.json.gz", // CLD + @"FA.json.gz", // CLI + @"F5.json.gz", // CMC ]]; NSSet *ignoreList = nil; From 1b9e6e8c8ebfe80af34dadeeafa5a63cff59a54e Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 9 Oct 2023 14:27:02 -0400 Subject: [PATCH 040/128] Add DAA, which doesn't yet pass all tests. --- .../Implementation/PerformImplementation.hpp | 43 +++++++++++++++++++ OSBindings/Mac/Clock SignalTests/8088Tests.mm | 10 +++-- 2 files changed, 49 insertions(+), 4 deletions(-) diff --git a/InstructionSets/x86/Implementation/PerformImplementation.hpp b/InstructionSets/x86/Implementation/PerformImplementation.hpp index ba665a3bf..c1bd5cf75 100644 --- a/InstructionSets/x86/Implementation/PerformImplementation.hpp +++ b/InstructionSets/x86/Implementation/PerformImplementation.hpp @@ -292,6 +292,48 @@ inline void aas(CPU::RegisterPair16 &ax, Status &status) { ax.halves.low &= 0x0f; } +inline void daa(uint8_t &al, Status &status) { + /* + IF (((AL AND 0FH) > 9) or AF = 1) + THEN + AL ← AL + 6; + CF ← CF OR CarryFromLastAddition; (* CF OR carry from AL ← AL + 6 *) + AF ← 1; + ELSE + AF ← 0; + FI; + IF ((AL AND F0H) > 90H) or CF = 1) + THEN + AL ← AL + 60H; + CF ← 1; + ELSE + CF ← 0; + FI; + */ + /* + The CF and AF flags are set if the adjustment of the value results in a + decimal carry in either digit of the result (see the “Operation” section above). + The SF, ZF, and PF flags are set according to the result. The OF flag is undefined. + */ + if((al & 0x0f) > 0x09 || status.auxiliary_carry) { + status.carry |= al > 0xf9; + al += 0x06; + status.auxiliary_carry = 1; + } else { + status.auxiliary_carry = 0; + } + + if((al & 0xf0) > 0x90 || status.carry) { + al += 0x60; + status.carry = 1; + } else { + status.carry = 0; + } + + status.sign = al & 0x80; + status.zero = status.parity = al; +} + template void adc(IntT &destination, IntT source, Status &status) { /* @@ -455,6 +497,7 @@ template < 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::ADC: Primitive::adc(destination(), source(), status); break; case Operation::ADD: Primitive::add(destination(), source(), status); break; diff --git a/OSBindings/Mac/Clock SignalTests/8088Tests.mm b/OSBindings/Mac/Clock SignalTests/8088Tests.mm index ef484181d..2c7ff5344 100644 --- a/OSBindings/Mac/Clock SignalTests/8088Tests.mm +++ b/OSBindings/Mac/Clock SignalTests/8088Tests.mm @@ -275,6 +275,12 @@ struct FailedExecution { - (NSArray *)testFiles { NSString *path = [NSString stringWithUTF8String:TestSuiteHome]; NSSet *allowList = [NSSet setWithArray:@[ + @"37.json.gz", // AAA + @"3F.json.gz", // AAS + @"D4.json.gz", // AAM + @"D5.json.gz", // AAD + @"27.json.gz", // DAA + // ADC @"10.json.gz", @"11.json.gz", @"12.json.gz", @"13.json.gz", @"14.json.gz", @"15.json.gz", @"80.2.json.gz", @"81.2.json.gz", @"83.2.json.gz", @@ -291,10 +297,6 @@ struct FailedExecution { @"E8.json.gz", @"FF.2.json.gz", @"9A.json.gz", @"FF.3.json.gz", - @"37.json.gz", // AAA - @"3F.json.gz", // AAS - @"D4.json.gz", // AAM - @"D5.json.gz", // AAD @"98.json.gz", // CBW @"F8.json.gz", // CLC @"FC.json.gz", // CLD From 0bf2099a70c6623e01a3177cb1c90289431127ff Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 9 Oct 2023 14:42:32 -0400 Subject: [PATCH 041/128] Improve DAA. --- .../Implementation/PerformImplementation.hpp | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/InstructionSets/x86/Implementation/PerformImplementation.hpp b/InstructionSets/x86/Implementation/PerformImplementation.hpp index c1bd5cf75..780cac885 100644 --- a/InstructionSets/x86/Implementation/PerformImplementation.hpp +++ b/InstructionSets/x86/Implementation/PerformImplementation.hpp @@ -294,15 +294,21 @@ inline void aas(CPU::RegisterPair16 &ax, Status &status) { inline void daa(uint8_t &al, Status &status) { /* + (as modified by https://www.felixcloutier.com/x86/daa ...) + + old_AL ← AL; + old_CF ← CF; + CF ← 0; + IF (((AL AND 0FH) > 9) or AF = 1) THEN AL ← AL + 6; - CF ← CF OR CarryFromLastAddition; (* CF OR carry from AL ← AL + 6 *) + CF ← old_CF OR CarryFromLastAddition; (* CF OR carry from AL ← AL + 6 *) AF ← 1; ELSE AF ← 0; FI; - IF ((AL AND F0H) > 90H) or CF = 1) + IF ((old_AL > 99H) or old_CF = 1) THEN AL ← AL + 60H; CF ← 1; @@ -315,15 +321,19 @@ inline void daa(uint8_t &al, Status &status) { decimal carry in either digit of the result (see the “Operation” section above). The SF, ZF, and PF flags are set according to the result. The OF flag is undefined. */ + const uint8_t old_al = al; + const auto old_carry = status.carry; + status.carry = 0; + if((al & 0x0f) > 0x09 || status.auxiliary_carry) { - status.carry |= al > 0xf9; + status.carry = old_carry | (al > 0xf9); al += 0x06; status.auxiliary_carry = 1; } else { status.auxiliary_carry = 0; } - if((al & 0xf0) > 0x90 || status.carry) { + if(old_al > 0x99 || old_carry) { al += 0x60; status.carry = 1; } else { From 6bbd896c34b08bc2ac1950d14fcda92f47d05d74 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 9 Oct 2023 14:47:39 -0400 Subject: [PATCH 042/128] Add DAS with a manageable number of failures. --- .../Implementation/PerformImplementation.hpp | 53 +++++++++++++++++++ OSBindings/Mac/Clock SignalTests/8088Tests.mm | 4 +- 2 files changed, 56 insertions(+), 1 deletion(-) diff --git a/InstructionSets/x86/Implementation/PerformImplementation.hpp b/InstructionSets/x86/Implementation/PerformImplementation.hpp index 780cac885..0101d1fd2 100644 --- a/InstructionSets/x86/Implementation/PerformImplementation.hpp +++ b/InstructionSets/x86/Implementation/PerformImplementation.hpp @@ -344,6 +344,58 @@ inline void daa(uint8_t &al, Status &status) { status.zero = status.parity = al; } +inline void das(uint8_t &al, Status &status) { + /* + (as modified by https://www.felixcloutier.com/x86/daa ...) + + old_AL ← AL; + old_CF ← CF; + CF ← 0; + + IF (((AL AND 0FH) > 9) or AF = 1) + THEN + AL ← AL - 6; + CF ← old_CF OR CarryFromLastAddition; (* CF OR borrow from AL ← AL - 6 *) + AF ← 1; + ELSE + AF ← 0; + FI; + IF ((old_AL > 99H) or old_CF = 1) + THEN + AL ← AL - 60H; + CF ← 1; + ELSE + CF ← 0; + FI; + */ + /* + The CF and AF flags are set if the adjustment of the value results in a + decimal carry in either digit of the result (see the “Operation” section above). + The SF, ZF, and PF flags are set according to the result. The OF flag is undefined. + */ + const uint8_t old_al = al; + const auto old_carry = status.carry; + status.carry = 0; + + if((al & 0x0f) > 0x09 || status.auxiliary_carry) { + status.carry = old_carry | (al < 0x06); + al -= 0x06; + status.auxiliary_carry = 1; + } else { + status.auxiliary_carry = 0; + } + + if(old_al > 0x99 || old_carry) { + al -= 0x60; + status.carry = 1; + } else { + status.carry = 0; + } + + status.sign = al & 0x80; + status.zero = status.parity = al; +} + template void adc(IntT &destination, IntT source, Status &status) { /* @@ -508,6 +560,7 @@ template < 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::ADC: Primitive::adc(destination(), source(), status); break; case Operation::ADD: Primitive::add(destination(), source(), status); break; diff --git a/OSBindings/Mac/Clock SignalTests/8088Tests.mm b/OSBindings/Mac/Clock SignalTests/8088Tests.mm index 2c7ff5344..1d5d8ea0f 100644 --- a/OSBindings/Mac/Clock SignalTests/8088Tests.mm +++ b/OSBindings/Mac/Clock SignalTests/8088Tests.mm @@ -280,6 +280,9 @@ struct FailedExecution { @"D4.json.gz", // AAM @"D5.json.gz", // AAD @"27.json.gz", // DAA + @"2F.json.gz", // DAS + + @"98.json.gz", // CBW // ADC @"10.json.gz", @"11.json.gz", @"12.json.gz", @"13.json.gz", @"14.json.gz", @"15.json.gz", @@ -297,7 +300,6 @@ struct FailedExecution { @"E8.json.gz", @"FF.2.json.gz", @"9A.json.gz", @"FF.3.json.gz", - @"98.json.gz", // CBW @"F8.json.gz", // CLC @"FC.json.gz", // CLD @"FA.json.gz", // CLI From 08aed3bac51f2efc30d02e5026673ae98b78f62d Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 9 Oct 2023 14:54:14 -0400 Subject: [PATCH 043/128] Implement CWD. --- .../x86/Implementation/PerformImplementation.hpp | 15 ++++++++++++++- OSBindings/Mac/Clock SignalTests/8088Tests.mm | 1 + 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/InstructionSets/x86/Implementation/PerformImplementation.hpp b/InstructionSets/x86/Implementation/PerformImplementation.hpp index 0101d1fd2..0a94c062c 100644 --- a/InstructionSets/x86/Implementation/PerformImplementation.hpp +++ b/InstructionSets/x86/Implementation/PerformImplementation.hpp @@ -497,6 +497,11 @@ inline void cbw(CPU::RegisterPair16 &ax) { ax.halves.high = (ax.halves.low & 0x80) ? 0xff : 0x00; } +template +inline void cwd(IntT &dx, IntT ax) { + dx = ax & top_bit() ? IntT(~0) : IntT(0); +} + inline void clc(Status &status) { status.carry = 0; } inline void cld(Status &status) { status.direction = 0; } inline void cli(Status &status) { status.interrupt = 0; } // TODO: quite a bit more in protected mode. @@ -562,6 +567,15 @@ template < case Operation::DAA: Primitive::daa(registers.al(), status); return; case Operation::DAS: Primitive::das(registers.al(), status); return; + case Operation::CBW: Primitive::cbw(registers.axp()); return; + case Operation::CWD: + if constexpr (data_size == DataSize::Word) { + Primitive::cwd(registers.dx(), registers.ax()); + } else if constexpr (is_32bit(model) && data_size == DataSize::DWord) { + Primitive::cwd(registers.edx(), registers.eax()); + } + return; + case Operation::ADC: Primitive::adc(destination(), source(), status); break; case Operation::ADD: Primitive::add(destination(), source(), status); break; case Operation::AND: Primitive::and_(destination(), source(), status); break; @@ -576,7 +590,6 @@ template < Primitive::call_far(instruction, flow_controller, registers, memory); return; - case Operation::CBW: Primitive::cbw(registers.axp()); return; case Operation::CLC: Primitive::clc(status); return; case Operation::CLD: Primitive::cld(status); return; case Operation::CLI: Primitive::cli(status); return; diff --git a/OSBindings/Mac/Clock SignalTests/8088Tests.mm b/OSBindings/Mac/Clock SignalTests/8088Tests.mm index 1d5d8ea0f..0b8ac54ce 100644 --- a/OSBindings/Mac/Clock SignalTests/8088Tests.mm +++ b/OSBindings/Mac/Clock SignalTests/8088Tests.mm @@ -283,6 +283,7 @@ struct FailedExecution { @"2F.json.gz", // DAS @"98.json.gz", // CBW + @"99.json.gz", // CWD // ADC @"10.json.gz", @"11.json.gz", @"12.json.gz", @"13.json.gz", @"14.json.gz", @"15.json.gz", From fe6e2eb0a18071f3a54a3046f595c5dd36f04eba Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 9 Oct 2023 15:00:04 -0400 Subject: [PATCH 044/128] Generalise CBW. --- .../Implementation/PerformImplementation.hpp | 22 +++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/InstructionSets/x86/Implementation/PerformImplementation.hpp b/InstructionSets/x86/Implementation/PerformImplementation.hpp index 0a94c062c..d5ef63560 100644 --- a/InstructionSets/x86/Implementation/PerformImplementation.hpp +++ b/InstructionSets/x86/Implementation/PerformImplementation.hpp @@ -493,12 +493,20 @@ void call_far(InstructionT &instruction, flow_controller.call(segment, offset); } -inline void cbw(CPU::RegisterPair16 &ax) { - ax.halves.high = (ax.halves.low & 0x80) ? 0xff : 0x00; +template +void cbw(IntT &ax) { + constexpr IntT test_bit = 1 << (sizeof(IntT) * 4 - 1); + constexpr IntT low_half = (1 << (sizeof(IntT) * 4)) - 1; + + if(ax & test_bit) { + ax |= ~low_half; + } else { + ax &= low_half; + } } template -inline void cwd(IntT &dx, IntT ax) { +void cwd(IntT &dx, IntT ax) { dx = ax & top_bit() ? IntT(~0) : IntT(0); } @@ -567,7 +575,13 @@ template < case Operation::DAA: Primitive::daa(registers.al(), status); return; case Operation::DAS: Primitive::das(registers.al(), status); return; - case Operation::CBW: Primitive::cbw(registers.axp()); return; + case Operation::CBW: + if constexpr (data_size == DataSize::Word) { + Primitive::cbw(registers.ax()); + } else if constexpr (is_32bit(model) && data_size == DataSize::DWord) { + Primitive::cbw(registers.eax()); + } + return; case Operation::CWD: if constexpr (data_size == DataSize::Word) { Primitive::cwd(registers.dx(), registers.ax()); From d24fa381c7a4699fe9afca852147a6f914e821cc Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 9 Oct 2023 15:03:01 -0400 Subject: [PATCH 045/128] 'Implement' ESC, NOP. --- .../x86/Implementation/PerformImplementation.hpp | 3 +++ OSBindings/Mac/Clock SignalTests/8088Tests.mm | 7 +++++++ 2 files changed, 10 insertions(+) diff --git a/InstructionSets/x86/Implementation/PerformImplementation.hpp b/InstructionSets/x86/Implementation/PerformImplementation.hpp index d5ef63560..6c09d50ba 100644 --- a/InstructionSets/x86/Implementation/PerformImplementation.hpp +++ b/InstructionSets/x86/Implementation/PerformImplementation.hpp @@ -590,6 +590,9 @@ template < } return; + case Operation::ESC: + case Operation::NOP: return; + case Operation::ADC: Primitive::adc(destination(), source(), status); break; case Operation::ADD: Primitive::add(destination(), source(), status); break; case Operation::AND: Primitive::and_(destination(), source(), status); break; diff --git a/OSBindings/Mac/Clock SignalTests/8088Tests.mm b/OSBindings/Mac/Clock SignalTests/8088Tests.mm index 0b8ac54ce..59f35beb9 100644 --- a/OSBindings/Mac/Clock SignalTests/8088Tests.mm +++ b/OSBindings/Mac/Clock SignalTests/8088Tests.mm @@ -285,6 +285,13 @@ struct FailedExecution { @"98.json.gz", // CBW @"99.json.gz", // CWD + // ESC + @"D8.json.gz", @"D9.json.gz", @"DA.json.gz", @"DB.json.gz", + @"DC.json.gz", @"DD.json.gz", @"DE.json.gz", @"DE.json.gz", + + // NOP + @"90.json.gz", + // ADC @"10.json.gz", @"11.json.gz", @"12.json.gz", @"13.json.gz", @"14.json.gz", @"15.json.gz", @"80.2.json.gz", @"81.2.json.gz", @"83.2.json.gz", From 67d364cc89351d910274167a57e12de5938b62ab Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 9 Oct 2023 16:21:04 -0400 Subject: [PATCH 046/128] Add faulty SUB, SBB. --- .../Implementation/PerformImplementation.hpp | 44 +++++++++++ OSBindings/Mac/Clock SignalTests/8088Tests.mm | 77 +++++++++++-------- 2 files changed, 88 insertions(+), 33 deletions(-) diff --git a/InstructionSets/x86/Implementation/PerformImplementation.hpp b/InstructionSets/x86/Implementation/PerformImplementation.hpp index 6c09d50ba..593980bf0 100644 --- a/InstructionSets/x86/Implementation/PerformImplementation.hpp +++ b/InstructionSets/x86/Implementation/PerformImplementation.hpp @@ -434,6 +434,44 @@ void add(IntT &destination, IntT source, Status &status) { destination = result; } +template +void sbb(IntT &destination, IntT source, Status &status) { + /* + DEST ← DEST - (SRC + CF); + */ + /* + The OF, SF, ZF, AF, CF, and PF flags are set according to the result. + */ + const IntT result = destination + source + status.carry_bit(); + + status.carry = !Numeric::carried_out() - 1>(destination, IntT(~source), result); + status.auxiliary_carry = !Numeric::carried_in<4>(destination, IntT(~source), result); + status.sign = result & top_bit(); + status.zero = status.parity = result; + status.overflow = overflow(destination, source, result); + + destination = result; +} + +template +void sub(IntT &destination, IntT source, Status &status) { + /* + DEST ← DEST - SRC; + */ + /* + The OF, SF, ZF, AF, CF, and PF flags are set according to the result. + */ + const IntT result = destination - source; + + status.carry = !Numeric::carried_out() - 1>(destination, IntT(~source), result); + status.auxiliary_carry = !Numeric::carried_in<4>(destination, IntT(~source), result); + status.sign = result & top_bit(); + status.zero = status.parity = result; + status.overflow = overflow(destination, source, result); + + destination = result; +} + template void and_(IntT &destination, IntT source, Status &status) { /* @@ -593,8 +631,14 @@ template < case Operation::ESC: case Operation::NOP: return; + case Operation::HLT: flow_controller.halt(); return; + case Operation::WAIT: flow_controller.wait(); return; + case Operation::ADC: Primitive::adc(destination(), source(), status); break; case Operation::ADD: Primitive::add(destination(), source(), status); break; + case Operation::SBB: Primitive::sbb(destination(), source(), status); break; + case Operation::SUB: Primitive::sub(destination(), source(), status); break; + case Operation::AND: Primitive::and_(destination(), source(), status); break; case Operation::CALLrel: diff --git a/OSBindings/Mac/Clock SignalTests/8088Tests.mm b/OSBindings/Mac/Clock SignalTests/8088Tests.mm index 59f35beb9..8c0831006 100644 --- a/OSBindings/Mac/Clock SignalTests/8088Tests.mm +++ b/OSBindings/Mac/Clock SignalTests/8088Tests.mm @@ -220,6 +220,9 @@ class FlowController { registers_.ip_ = offset; } + void halt() {} + void wait() {} + private: Memory &memory_; Registers ®isters_; @@ -275,43 +278,51 @@ struct FailedExecution { - (NSArray *)testFiles { NSString *path = [NSString stringWithUTF8String:TestSuiteHome]; NSSet *allowList = [NSSet setWithArray:@[ - @"37.json.gz", // AAA - @"3F.json.gz", // AAS - @"D4.json.gz", // AAM - @"D5.json.gz", // AAD - @"27.json.gz", // DAA - @"2F.json.gz", // DAS +// @"37.json.gz", // AAA +// @"3F.json.gz", // AAS +// @"D4.json.gz", // AAM +// @"D5.json.gz", // AAD +// @"27.json.gz", // DAA +// @"2F.json.gz", // DAS +// +// @"98.json.gz", // CBW +// @"99.json.gz", // CWD +// +// // ESC +// @"D8.json.gz", @"D9.json.gz", @"DA.json.gz", @"DB.json.gz", +// @"DC.json.gz", @"DD.json.gz", @"DE.json.gz", @"DE.json.gz", +// +// // NOP +// @"90.json.gz", +// +// // ADC +// @"10.json.gz", @"11.json.gz", @"12.json.gz", @"13.json.gz", @"14.json.gz", @"15.json.gz", +// @"80.2.json.gz", @"81.2.json.gz", @"83.2.json.gz", +// +// // ADD +// @"00.json.gz", @"01.json.gz", @"02.json.gz", @"03.json.gz", @"04.json.gz", @"05.json.gz", +// @"80.0.json.gz", @"81.0.json.gz", @"83.0.json.gz", - @"98.json.gz", // CBW - @"99.json.gz", // CWD + // SBB +// @"18.json.gz", @"19.json.gz", @"1A.json.gz", @"1B.json.gz", @"1C.json.gz", @"1D.json.gz", +// @"80.3.json.gz", @"81.3.json.gz", @"83.3.json.gz", - // ESC - @"D8.json.gz", @"D9.json.gz", @"DA.json.gz", @"DB.json.gz", - @"DC.json.gz", @"DD.json.gz", @"DE.json.gz", @"DE.json.gz", - - // NOP - @"90.json.gz", - - // ADC - @"10.json.gz", @"11.json.gz", @"12.json.gz", @"13.json.gz", @"14.json.gz", @"15.json.gz", - @"80.2.json.gz", @"81.2.json.gz", @"83.2.json.gz", - - // ADD - @"00.json.gz", @"01.json.gz", @"02.json.gz", @"03.json.gz", @"04.json.gz", @"05.json.gz", - @"80.0.json.gz", @"81.0.json.gz", @"83.0.json.gz", + // SUB + @"28.json.gz", //@"29.json.gz", @"2A.json.gz", @"2B.json.gz", @"2C.json.gz", @"2D.json.gz", +// @"80.5.json.gz", @"81.5.json.gz", @"83.5.json.gz", // AND - @"20.json.gz", @"21.json.gz", @"22.json.gz", @"23.json.gz", @"24.json.gz", @"25.json.gz", - @"80.4.json.gz", @"81.4.json.gz", @"83.4.json.gz", - - // CALL - @"E8.json.gz", @"FF.2.json.gz", - @"9A.json.gz", @"FF.3.json.gz", - - @"F8.json.gz", // CLC - @"FC.json.gz", // CLD - @"FA.json.gz", // CLI - @"F5.json.gz", // CMC +// @"20.json.gz", @"21.json.gz", @"22.json.gz", @"23.json.gz", @"24.json.gz", @"25.json.gz", +// @"80.4.json.gz", @"81.4.json.gz", @"83.4.json.gz", +// +// // CALL +// @"E8.json.gz", @"FF.2.json.gz", +// @"9A.json.gz", @"FF.3.json.gz", +// +// @"F8.json.gz", // CLC +// @"FC.json.gz", // CLD +// @"FA.json.gz", // CLI +// @"F5.json.gz", // CMC ]]; NSSet *ignoreList = nil; From 58aa1da6499a5057b06fce0c829dd884b83c8385 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 9 Oct 2023 16:30:47 -0400 Subject: [PATCH 047/128] Fix SUB. SBB still failing. --- .../x86/Implementation/PerformImplementation.hpp | 4 +++- OSBindings/Mac/Clock SignalTests/8088Tests.mm | 8 ++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/InstructionSets/x86/Implementation/PerformImplementation.hpp b/InstructionSets/x86/Implementation/PerformImplementation.hpp index 593980bf0..bd36a79cd 100644 --- a/InstructionSets/x86/Implementation/PerformImplementation.hpp +++ b/InstructionSets/x86/Implementation/PerformImplementation.hpp @@ -172,7 +172,7 @@ template constexpr int bit_size() { /// and the result was @c result. All other bits will be clear. template IntT overflow(IntT lhs, IntT rhs, IntT result) { - const IntT output_changed = result ^ rhs; + const IntT output_changed = result ^ lhs; const IntT input_differed = lhs ^ rhs; if constexpr (is_add) { @@ -181,6 +181,8 @@ IntT overflow(IntT lhs, IntT rhs, IntT result) { return top_bit() & output_changed & input_differed; } } +// NOTE TO FUTURE SELF: the original 68k `overflow` treats lhs and rhs the other way +// around, affecting subtractive overflow. Be careful. // // END COPY AND PASTE SECTION. diff --git a/OSBindings/Mac/Clock SignalTests/8088Tests.mm b/OSBindings/Mac/Clock SignalTests/8088Tests.mm index 8c0831006..00ab3b4b8 100644 --- a/OSBindings/Mac/Clock SignalTests/8088Tests.mm +++ b/OSBindings/Mac/Clock SignalTests/8088Tests.mm @@ -304,12 +304,12 @@ struct FailedExecution { // @"80.0.json.gz", @"81.0.json.gz", @"83.0.json.gz", // SBB -// @"18.json.gz", @"19.json.gz", @"1A.json.gz", @"1B.json.gz", @"1C.json.gz", @"1D.json.gz", -// @"80.3.json.gz", @"81.3.json.gz", @"83.3.json.gz", + @"18.json.gz", @"19.json.gz", @"1A.json.gz", @"1B.json.gz", @"1C.json.gz", @"1D.json.gz", + @"80.3.json.gz", @"81.3.json.gz", @"83.3.json.gz", // SUB - @"28.json.gz", //@"29.json.gz", @"2A.json.gz", @"2B.json.gz", @"2C.json.gz", @"2D.json.gz", -// @"80.5.json.gz", @"81.5.json.gz", @"83.5.json.gz", + @"28.json.gz", @"29.json.gz", @"2A.json.gz", @"2B.json.gz", @"2C.json.gz", @"2D.json.gz", + @"80.5.json.gz", @"81.5.json.gz", @"83.5.json.gz", // AND // @"20.json.gz", @"21.json.gz", @"22.json.gz", @"23.json.gz", @"24.json.gz", @"25.json.gz", From f74ca8aee1e9fe3d737716a139455ab7888577fe Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 9 Oct 2023 16:32:01 -0400 Subject: [PATCH 048/128] Fix SBB. --- .../Implementation/PerformImplementation.hpp | 2 +- OSBindings/Mac/Clock SignalTests/8088Tests.mm | 70 +++++++++---------- 2 files changed, 36 insertions(+), 36 deletions(-) diff --git a/InstructionSets/x86/Implementation/PerformImplementation.hpp b/InstructionSets/x86/Implementation/PerformImplementation.hpp index bd36a79cd..70b6de0c8 100644 --- a/InstructionSets/x86/Implementation/PerformImplementation.hpp +++ b/InstructionSets/x86/Implementation/PerformImplementation.hpp @@ -444,7 +444,7 @@ void sbb(IntT &destination, IntT source, Status &status) { /* The OF, SF, ZF, AF, CF, and PF flags are set according to the result. */ - const IntT result = destination + source + status.carry_bit(); + const IntT result = destination - source - status.carry_bit(); status.carry = !Numeric::carried_out() - 1>(destination, IntT(~source), result); status.auxiliary_carry = !Numeric::carried_in<4>(destination, IntT(~source), result); diff --git a/OSBindings/Mac/Clock SignalTests/8088Tests.mm b/OSBindings/Mac/Clock SignalTests/8088Tests.mm index 00ab3b4b8..aa2bb61de 100644 --- a/OSBindings/Mac/Clock SignalTests/8088Tests.mm +++ b/OSBindings/Mac/Clock SignalTests/8088Tests.mm @@ -278,30 +278,30 @@ struct FailedExecution { - (NSArray *)testFiles { NSString *path = [NSString stringWithUTF8String:TestSuiteHome]; NSSet *allowList = [NSSet setWithArray:@[ -// @"37.json.gz", // AAA -// @"3F.json.gz", // AAS -// @"D4.json.gz", // AAM -// @"D5.json.gz", // AAD -// @"27.json.gz", // DAA -// @"2F.json.gz", // DAS -// -// @"98.json.gz", // CBW -// @"99.json.gz", // CWD -// -// // ESC -// @"D8.json.gz", @"D9.json.gz", @"DA.json.gz", @"DB.json.gz", -// @"DC.json.gz", @"DD.json.gz", @"DE.json.gz", @"DE.json.gz", -// -// // NOP -// @"90.json.gz", -// -// // ADC -// @"10.json.gz", @"11.json.gz", @"12.json.gz", @"13.json.gz", @"14.json.gz", @"15.json.gz", -// @"80.2.json.gz", @"81.2.json.gz", @"83.2.json.gz", -// -// // ADD -// @"00.json.gz", @"01.json.gz", @"02.json.gz", @"03.json.gz", @"04.json.gz", @"05.json.gz", -// @"80.0.json.gz", @"81.0.json.gz", @"83.0.json.gz", + @"37.json.gz", // AAA + @"3F.json.gz", // AAS + @"D4.json.gz", // AAM + @"D5.json.gz", // AAD + @"27.json.gz", // DAA + @"2F.json.gz", // DAS + + @"98.json.gz", // CBW + @"99.json.gz", // CWD + + // ESC + @"D8.json.gz", @"D9.json.gz", @"DA.json.gz", @"DB.json.gz", + @"DC.json.gz", @"DD.json.gz", @"DE.json.gz", @"DE.json.gz", + + // NOP + @"90.json.gz", + + // ADC + @"10.json.gz", @"11.json.gz", @"12.json.gz", @"13.json.gz", @"14.json.gz", @"15.json.gz", + @"80.2.json.gz", @"81.2.json.gz", @"83.2.json.gz", + + // ADD + @"00.json.gz", @"01.json.gz", @"02.json.gz", @"03.json.gz", @"04.json.gz", @"05.json.gz", + @"80.0.json.gz", @"81.0.json.gz", @"83.0.json.gz", // SBB @"18.json.gz", @"19.json.gz", @"1A.json.gz", @"1B.json.gz", @"1C.json.gz", @"1D.json.gz", @@ -312,17 +312,17 @@ struct FailedExecution { @"80.5.json.gz", @"81.5.json.gz", @"83.5.json.gz", // AND -// @"20.json.gz", @"21.json.gz", @"22.json.gz", @"23.json.gz", @"24.json.gz", @"25.json.gz", -// @"80.4.json.gz", @"81.4.json.gz", @"83.4.json.gz", -// -// // CALL -// @"E8.json.gz", @"FF.2.json.gz", -// @"9A.json.gz", @"FF.3.json.gz", -// -// @"F8.json.gz", // CLC -// @"FC.json.gz", // CLD -// @"FA.json.gz", // CLI -// @"F5.json.gz", // CMC + @"20.json.gz", @"21.json.gz", @"22.json.gz", @"23.json.gz", @"24.json.gz", @"25.json.gz", + @"80.4.json.gz", @"81.4.json.gz", @"83.4.json.gz", + + // CALL + @"E8.json.gz", @"FF.2.json.gz", + @"9A.json.gz", @"FF.3.json.gz", + + @"F8.json.gz", // CLC + @"FC.json.gz", // CLD + @"FA.json.gz", // CLI + @"F5.json.gz", // CMC ]]; NSSet *ignoreList = nil; From 1cb26cb141f6394a763ee06e2a1704f160d6440b Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 9 Oct 2023 16:40:50 -0400 Subject: [PATCH 049/128] Pull add/sub distinction into templates. --- .../Implementation/PerformImplementation.hpp | 16 +++++----- Numeric/Carry.hpp | 31 ++++++++++++++++--- .../Implementation/6502Implementation.hpp | 8 ++--- 3 files changed, 38 insertions(+), 17 deletions(-) diff --git a/InstructionSets/x86/Implementation/PerformImplementation.hpp b/InstructionSets/x86/Implementation/PerformImplementation.hpp index 70b6de0c8..1978a54a0 100644 --- a/InstructionSets/x86/Implementation/PerformImplementation.hpp +++ b/InstructionSets/x86/Implementation/PerformImplementation.hpp @@ -408,8 +408,8 @@ void adc(IntT &destination, IntT source, Status &status) { */ const IntT result = destination + source + status.carry_bit(); - status.carry = Numeric::carried_out() - 1>(destination, source, result); - status.auxiliary_carry = Numeric::carried_in<4>(destination, source, result); + status.carry = Numeric::carried_out() - 1>(destination, source, result); + status.auxiliary_carry = Numeric::carried_in(destination, source, result); status.sign = result & top_bit(); status.zero = status.parity = result; status.overflow = overflow(destination, source, result); @@ -427,8 +427,8 @@ void add(IntT &destination, IntT source, Status &status) { */ const IntT result = destination + source; - status.carry = Numeric::carried_out() - 1>(destination, source, result); - status.auxiliary_carry = Numeric::carried_in<4>(destination, source, result); + status.carry = Numeric::carried_out() - 1>(destination, source, result); + status.auxiliary_carry = Numeric::carried_in(destination, source, result); status.sign = result & top_bit(); status.zero = status.parity = result; status.overflow = overflow(destination, source, result); @@ -446,8 +446,8 @@ void sbb(IntT &destination, IntT source, Status &status) { */ const IntT result = destination - source - status.carry_bit(); - status.carry = !Numeric::carried_out() - 1>(destination, IntT(~source), result); - status.auxiliary_carry = !Numeric::carried_in<4>(destination, IntT(~source), result); + status.carry = Numeric::carried_out() - 1>(destination, source, result); + status.auxiliary_carry = Numeric::carried_in(destination, source, result); status.sign = result & top_bit(); status.zero = status.parity = result; status.overflow = overflow(destination, source, result); @@ -465,8 +465,8 @@ void sub(IntT &destination, IntT source, Status &status) { */ const IntT result = destination - source; - status.carry = !Numeric::carried_out() - 1>(destination, IntT(~source), result); - status.auxiliary_carry = !Numeric::carried_in<4>(destination, IntT(~source), result); + status.carry = Numeric::carried_out() - 1>(destination, source, result); + status.auxiliary_carry = Numeric::carried_in(destination, source, result); status.sign = result & top_bit(); status.zero = status.parity = result; status.overflow = overflow(destination, source, result); diff --git a/Numeric/Carry.hpp b/Numeric/Carry.hpp index fd17ed357..4896220b9 100644 --- a/Numeric/Carry.hpp +++ b/Numeric/Carry.hpp @@ -11,19 +11,40 @@ namespace Numeric { -/// @returns @c true if there was carry out of @c bit when @c source1 and @c source2 were added, producing @c result. -template bool carried_out(IntT source1, IntT source2, IntT result) { +/// @returns @c true if there, from @c bit there was +/// • carry after calculating @c lhs + @c rhs if @c is_add is true; or +/// • borrow after calculating @c lhs - @c rhs if @c is_add is false; +/// producing @c result. +template bool carried_out(IntT lhs, IntT rhs, IntT result) { // 0 and 0 => didn't. // 0 and 1 or 1 and 0 => did if 0. // 1 and 1 => did. - return IntT(1 << bit) & (source1 | source2) & ((source1 & source2) | ~result); + if constexpr (!is_add) { + rhs = ~rhs; + } + const bool carry = IntT(1 << bit) & (lhs | rhs) & ((lhs & rhs) | ~result); + if constexpr (!is_add) { + return !carry; + } else { + return carry; + } } +// ~carried_out<>(d, ~s, r) + /// @returns @c true if there was carry into @c bit when @c source1 and @c source2 were added, producing @c result. -template bool carried_in(IntT source1, IntT source2, IntT result) { +template bool carried_in(IntT lhs, IntT rhs, IntT result) { // 0 and 0 or 1 and 1 => did if 1 // 0 and 1 or 1 and 0 => did if 0 - return IntT(1 << bit) & (source1 ^ source2 ^ result); + if constexpr (!is_add) { + rhs = ~rhs; + } + const bool carry = IntT(1 << bit) & (lhs ^ rhs ^ result); + if constexpr (!is_add) { + return !carry; + } else { + return carry; + } } } diff --git a/Processors/6502/Implementation/6502Implementation.hpp b/Processors/6502/Implementation/6502Implementation.hpp index bd8dbd502..caa9c71e5 100644 --- a/Processors/6502/Implementation/6502Implementation.hpp +++ b/Processors/6502/Implementation/6502Implementation.hpp @@ -327,7 +327,7 @@ template void Proces // All flags are set based only on the decimal result. flags_.zero_result = result; - flags_.carry = Numeric::carried_out<7>(a_, operand_, result); + flags_.carry = Numeric::carried_out(a_, operand_, result); flags_.negative_result = result; flags_.overflow = (( (result ^ a_) & (result ^ operand_) ) & 0x80) >> 1; @@ -343,7 +343,7 @@ template void Proces // on a 6502 additional borrow isn't propagated but on a 65C02 it is. // This difference affects invalid BCD numbers only — valid numbers will // never be less than -9 so adding 10 will always generate carry. - if(!Numeric::carried_in<4>(a_, operand_, result)) { + if(!Numeric::carried_in(a_, operand_, result)) { if constexpr (is_65c02(personality)) { result += 0xfa; } else { @@ -377,7 +377,7 @@ template void Proces if(flags_.decimal && has_decimal_mode(personality)) { uint8_t result = a_ + operand_ + flags_.carry; flags_.zero_result = result; - flags_.carry = Numeric::carried_out<7>(a_, operand_, result); + flags_.carry = Numeric::carried_out(a_, operand_, result); // General ADC logic: // @@ -390,7 +390,7 @@ template void Proces // // So if that carry already happened, fix up the bottom without permitting another; // otherwise permit the carry to happen (and check whether carry then rippled out of bit 7). - if(Numeric::carried_in<4>(a_, operand_, result)) { + if(Numeric::carried_in(a_, operand_, result)) { result = (result & 0xf0) | ((result + 0x06) & 0x0f); } else if((result & 0xf) > 0x9) { flags_.carry |= result >= 0x100 - 0x6; From e46e42d89653a7e3b23b96f24322347d9e3378c3 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 9 Oct 2023 16:47:02 -0400 Subject: [PATCH 050/128] This is the same test either way around. --- .../Implementation/PerformImplementation.hpp | 8 +-- Numeric/Carry.hpp | 25 +++---- OSBindings/Mac/Clock SignalTests/8088Tests.mm | 70 +++++++++---------- .../Implementation/6502Implementation.hpp | 4 +- 4 files changed, 50 insertions(+), 57 deletions(-) diff --git a/InstructionSets/x86/Implementation/PerformImplementation.hpp b/InstructionSets/x86/Implementation/PerformImplementation.hpp index 1978a54a0..655a9aef2 100644 --- a/InstructionSets/x86/Implementation/PerformImplementation.hpp +++ b/InstructionSets/x86/Implementation/PerformImplementation.hpp @@ -409,7 +409,7 @@ void adc(IntT &destination, IntT source, Status &status) { const IntT result = destination + source + status.carry_bit(); status.carry = Numeric::carried_out() - 1>(destination, source, result); - status.auxiliary_carry = Numeric::carried_in(destination, source, result); + status.auxiliary_carry = Numeric::carried_in<4>(destination, source, result); status.sign = result & top_bit(); status.zero = status.parity = result; status.overflow = overflow(destination, source, result); @@ -428,7 +428,7 @@ void add(IntT &destination, IntT source, Status &status) { const IntT result = destination + source; status.carry = Numeric::carried_out() - 1>(destination, source, result); - status.auxiliary_carry = Numeric::carried_in(destination, source, result); + status.auxiliary_carry = Numeric::carried_in<4>(destination, source, result); status.sign = result & top_bit(); status.zero = status.parity = result; status.overflow = overflow(destination, source, result); @@ -447,7 +447,7 @@ void sbb(IntT &destination, IntT source, Status &status) { const IntT result = destination - source - status.carry_bit(); status.carry = Numeric::carried_out() - 1>(destination, source, result); - status.auxiliary_carry = Numeric::carried_in(destination, source, result); + status.auxiliary_carry = Numeric::carried_in<4>(destination, source, result); status.sign = result & top_bit(); status.zero = status.parity = result; status.overflow = overflow(destination, source, result); @@ -466,7 +466,7 @@ void sub(IntT &destination, IntT source, Status &status) { const IntT result = destination - source; status.carry = Numeric::carried_out() - 1>(destination, source, result); - status.auxiliary_carry = Numeric::carried_in(destination, source, result); + status.auxiliary_carry = Numeric::carried_in<4>(destination, source, result); status.sign = result & top_bit(); status.zero = status.parity = result; status.overflow = overflow(destination, source, result); diff --git a/Numeric/Carry.hpp b/Numeric/Carry.hpp index 4896220b9..2dbf86e54 100644 --- a/Numeric/Carry.hpp +++ b/Numeric/Carry.hpp @@ -11,7 +11,7 @@ namespace Numeric { -/// @returns @c true if there, from @c bit there was +/// @returns @c true if from @c bit there was: /// • carry after calculating @c lhs + @c rhs if @c is_add is true; or /// • borrow after calculating @c lhs - @c rhs if @c is_add is false; /// producing @c result. @@ -30,21 +30,14 @@ template bool carried_out(IntT lhs, IntT r } } -// ~carried_out<>(d, ~s, r) - -/// @returns @c true if there was carry into @c bit when @c source1 and @c source2 were added, producing @c result. -template bool carried_in(IntT lhs, IntT rhs, IntT result) { - // 0 and 0 or 1 and 1 => did if 1 - // 0 and 1 or 1 and 0 => did if 0 - if constexpr (!is_add) { - rhs = ~rhs; - } - const bool carry = IntT(1 << bit) & (lhs ^ rhs ^ result); - if constexpr (!is_add) { - return !carry; - } else { - return carry; - } +/// @returns @c true if there was carry into @c bit when computing either: +/// • @c lhs + @c rhs; or +/// • @c lhs - @c rhs; +/// producing @c result. +template bool carried_in(IntT lhs, IntT rhs, IntT result) { + // 0 and 0 or 1 and 1 => did if 1. + // 0 and 1 or 1 and 0 => did if 0. + return IntT(1 << bit) & (lhs ^ rhs ^ result); } } diff --git a/OSBindings/Mac/Clock SignalTests/8088Tests.mm b/OSBindings/Mac/Clock SignalTests/8088Tests.mm index aa2bb61de..00ab3b4b8 100644 --- a/OSBindings/Mac/Clock SignalTests/8088Tests.mm +++ b/OSBindings/Mac/Clock SignalTests/8088Tests.mm @@ -278,30 +278,30 @@ struct FailedExecution { - (NSArray *)testFiles { NSString *path = [NSString stringWithUTF8String:TestSuiteHome]; NSSet *allowList = [NSSet setWithArray:@[ - @"37.json.gz", // AAA - @"3F.json.gz", // AAS - @"D4.json.gz", // AAM - @"D5.json.gz", // AAD - @"27.json.gz", // DAA - @"2F.json.gz", // DAS - - @"98.json.gz", // CBW - @"99.json.gz", // CWD - - // ESC - @"D8.json.gz", @"D9.json.gz", @"DA.json.gz", @"DB.json.gz", - @"DC.json.gz", @"DD.json.gz", @"DE.json.gz", @"DE.json.gz", - - // NOP - @"90.json.gz", - - // ADC - @"10.json.gz", @"11.json.gz", @"12.json.gz", @"13.json.gz", @"14.json.gz", @"15.json.gz", - @"80.2.json.gz", @"81.2.json.gz", @"83.2.json.gz", - - // ADD - @"00.json.gz", @"01.json.gz", @"02.json.gz", @"03.json.gz", @"04.json.gz", @"05.json.gz", - @"80.0.json.gz", @"81.0.json.gz", @"83.0.json.gz", +// @"37.json.gz", // AAA +// @"3F.json.gz", // AAS +// @"D4.json.gz", // AAM +// @"D5.json.gz", // AAD +// @"27.json.gz", // DAA +// @"2F.json.gz", // DAS +// +// @"98.json.gz", // CBW +// @"99.json.gz", // CWD +// +// // ESC +// @"D8.json.gz", @"D9.json.gz", @"DA.json.gz", @"DB.json.gz", +// @"DC.json.gz", @"DD.json.gz", @"DE.json.gz", @"DE.json.gz", +// +// // NOP +// @"90.json.gz", +// +// // ADC +// @"10.json.gz", @"11.json.gz", @"12.json.gz", @"13.json.gz", @"14.json.gz", @"15.json.gz", +// @"80.2.json.gz", @"81.2.json.gz", @"83.2.json.gz", +// +// // ADD +// @"00.json.gz", @"01.json.gz", @"02.json.gz", @"03.json.gz", @"04.json.gz", @"05.json.gz", +// @"80.0.json.gz", @"81.0.json.gz", @"83.0.json.gz", // SBB @"18.json.gz", @"19.json.gz", @"1A.json.gz", @"1B.json.gz", @"1C.json.gz", @"1D.json.gz", @@ -312,17 +312,17 @@ struct FailedExecution { @"80.5.json.gz", @"81.5.json.gz", @"83.5.json.gz", // AND - @"20.json.gz", @"21.json.gz", @"22.json.gz", @"23.json.gz", @"24.json.gz", @"25.json.gz", - @"80.4.json.gz", @"81.4.json.gz", @"83.4.json.gz", - - // CALL - @"E8.json.gz", @"FF.2.json.gz", - @"9A.json.gz", @"FF.3.json.gz", - - @"F8.json.gz", // CLC - @"FC.json.gz", // CLD - @"FA.json.gz", // CLI - @"F5.json.gz", // CMC +// @"20.json.gz", @"21.json.gz", @"22.json.gz", @"23.json.gz", @"24.json.gz", @"25.json.gz", +// @"80.4.json.gz", @"81.4.json.gz", @"83.4.json.gz", +// +// // CALL +// @"E8.json.gz", @"FF.2.json.gz", +// @"9A.json.gz", @"FF.3.json.gz", +// +// @"F8.json.gz", // CLC +// @"FC.json.gz", // CLD +// @"FA.json.gz", // CLI +// @"F5.json.gz", // CMC ]]; NSSet *ignoreList = nil; diff --git a/Processors/6502/Implementation/6502Implementation.hpp b/Processors/6502/Implementation/6502Implementation.hpp index caa9c71e5..b55b3596f 100644 --- a/Processors/6502/Implementation/6502Implementation.hpp +++ b/Processors/6502/Implementation/6502Implementation.hpp @@ -343,7 +343,7 @@ template void Proces // on a 6502 additional borrow isn't propagated but on a 65C02 it is. // This difference affects invalid BCD numbers only — valid numbers will // never be less than -9 so adding 10 will always generate carry. - if(!Numeric::carried_in(a_, operand_, result)) { + if(!Numeric::carried_in<4>(a_, operand_, result)) { if constexpr (is_65c02(personality)) { result += 0xfa; } else { @@ -390,7 +390,7 @@ template void Proces // // So if that carry already happened, fix up the bottom without permitting another; // otherwise permit the carry to happen (and check whether carry then rippled out of bit 7). - if(Numeric::carried_in(a_, operand_, result)) { + if(Numeric::carried_in<4>(a_, operand_, result)) { result = (result & 0xf0) | ((result + 0x06) & 0x0f); } else if((result & 0xf) > 0x9) { flags_.carry |= result >= 0x100 - 0x6; From ff6573dd02eee7e4a8d96a76d857f785e6159b19 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 9 Oct 2023 21:50:17 -0400 Subject: [PATCH 051/128] Implement MUL. --- .../Implementation/PerformImplementation.hpp | 37 ++++++++++++++++++- InstructionSets/x86/Instruction.hpp | 2 +- OSBindings/Mac/Clock SignalTests/8088Tests.mm | 25 ++++++++----- 3 files changed, 51 insertions(+), 13 deletions(-) diff --git a/InstructionSets/x86/Implementation/PerformImplementation.hpp b/InstructionSets/x86/Implementation/PerformImplementation.hpp index 655a9aef2..b7f0763ee 100644 --- a/InstructionSets/x86/Implementation/PerformImplementation.hpp +++ b/InstructionSets/x86/Implementation/PerformImplementation.hpp @@ -474,6 +474,28 @@ void sub(IntT &destination, IntT source, Status &status) { destination = result; } +template +void mul(IntT &destination_high, IntT &destination_low, IntT source, Status &status) { + /* + IF byte operation + THEN + AX ← AL * SRC + ELSE (* word or doubleword operation *) + IF OperandSize = 16 THEN + DX:AX ← AX * SRC + ELSE (* OperandSize = 32 *) + EDX:EAX ← EAX * SRC + FI; + */ + /* + The OF and CF flags are cleared to 0 if the upper half of the result is 0; + otherwise, they are set to 1. The SF, ZF, AF, and PF flags are undefined. + */ + destination_high = (destination_low * source) >> (8 * sizeof(IntT)); + destination_low *= source; + status.overflow = status.carry = destination_high; +} + template void and_(IntT &destination, IntT source, Status &status) { /* @@ -625,7 +647,7 @@ template < case Operation::CWD: if constexpr (data_size == DataSize::Word) { Primitive::cwd(registers.dx(), registers.ax()); - } else if constexpr (is_32bit(model) && data_size == DataSize::DWord) { + } else if constexpr (data_size == DataSize::DWord) { Primitive::cwd(registers.edx(), registers.eax()); } return; @@ -640,6 +662,15 @@ template < case Operation::ADD: Primitive::add(destination(), source(), status); break; case Operation::SBB: Primitive::sbb(destination(), source(), status); break; case Operation::SUB: Primitive::sub(destination(), source(), status); break; + case Operation::MUL: + if constexpr (data_size == DataSize::Byte) { + Primitive::mul(registers.ah(), registers.al(), source(), status); + } else if constexpr (data_size == DataSize::Word) { + Primitive::mul(registers.dx(), registers.ax(), source(), status); + } else if constexpr (data_size == DataSize::DWord) { + Primitive::mul(registers.edx(), registers.eax(), source(), status); + } + return; case Operation::AND: Primitive::and_(destination(), source(), status); break; @@ -689,7 +720,9 @@ template < perform(instruction, status, flow_controller, registers, memory, io); break; case DataSize::DWord: - perform(instruction, status, flow_controller, registers, memory, io); + if constexpr (is_32bit(model)) { + perform(instruction, status, flow_controller, registers, memory, io); + } break; case DataSize::None: perform(instruction, status, flow_controller, registers, memory, io); diff --git a/InstructionSets/x86/Instruction.hpp b/InstructionSets/x86/Instruction.hpp index ca7a37269..67a435cbb 100644 --- a/InstructionSets/x86/Instruction.hpp +++ b/InstructionSets/x86/Instruction.hpp @@ -69,7 +69,7 @@ enum class Operation: uint8_t { SBB, /// Subtract; source, destination, operand and displacement will be populated appropriately. SUB, - /// Unsigned multiply; multiplies the source value by AX or AL, storing the result in DX:AX or AX. + /// Unsigned multiply; multiplies the source value by EAX, AX or AL, storing the result in EDX:EAX, DX:AX or AX. MUL, /// Single operand signed multiply; multiplies the source value by AX or AL, storing the result in DX:AX or AX. IMUL_1, diff --git a/OSBindings/Mac/Clock SignalTests/8088Tests.mm b/OSBindings/Mac/Clock SignalTests/8088Tests.mm index 00ab3b4b8..1a741a5b1 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 { @@ -292,9 +292,8 @@ struct FailedExecution { // @"D8.json.gz", @"D9.json.gz", @"DA.json.gz", @"DB.json.gz", // @"DC.json.gz", @"DD.json.gz", @"DE.json.gz", @"DE.json.gz", // -// // NOP -// @"90.json.gz", -// +// // Untested: HLT, WAIT +//// // // ADC // @"10.json.gz", @"11.json.gz", @"12.json.gz", @"13.json.gz", @"14.json.gz", @"15.json.gz", // @"80.2.json.gz", @"81.2.json.gz", @"83.2.json.gz", @@ -302,14 +301,20 @@ struct FailedExecution { // // ADD // @"00.json.gz", @"01.json.gz", @"02.json.gz", @"03.json.gz", @"04.json.gz", @"05.json.gz", // @"80.0.json.gz", @"81.0.json.gz", @"83.0.json.gz", +// +// // SBB +// @"18.json.gz", @"19.json.gz", @"1A.json.gz", @"1B.json.gz", @"1C.json.gz", @"1D.json.gz", +// @"80.3.json.gz", @"81.3.json.gz", @"83.3.json.gz", +// +// // SUB +// @"28.json.gz", @"29.json.gz", @"2A.json.gz", @"2B.json.gz", @"2C.json.gz", @"2D.json.gz", +// @"80.5.json.gz", @"81.5.json.gz", @"83.5.json.gz", - // SBB - @"18.json.gz", @"19.json.gz", @"1A.json.gz", @"1B.json.gz", @"1C.json.gz", @"1D.json.gz", - @"80.3.json.gz", @"81.3.json.gz", @"83.3.json.gz", + // MUL + @"F6.4.json.gz", @"F7.4.json.gz", - // SUB - @"28.json.gz", @"29.json.gz", @"2A.json.gz", @"2B.json.gz", @"2C.json.gz", @"2D.json.gz", - @"80.5.json.gz", @"81.5.json.gz", @"83.5.json.gz", +// // NOP +// @"90.json.gz", // AND // @"20.json.gz", @"21.json.gz", @"22.json.gz", @"23.json.gz", @"24.json.gz", @"25.json.gz", From 5e830781ccaf8e8343fe71db023f9c1eb83e68af Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 9 Oct 2023 22:12:15 -0400 Subject: [PATCH 052/128] Implement IMUL, improve test memory footprint. --- .../Implementation/PerformImplementation.hpp | 44 +++++++++ InstructionSets/x86/Instruction.hpp | 2 +- OSBindings/Mac/Clock SignalTests/8088Tests.mm | 95 ++++++++++--------- 3 files changed, 94 insertions(+), 47 deletions(-) diff --git a/InstructionSets/x86/Implementation/PerformImplementation.hpp b/InstructionSets/x86/Implementation/PerformImplementation.hpp index b7f0763ee..dfbcfd72d 100644 --- a/InstructionSets/x86/Implementation/PerformImplementation.hpp +++ b/InstructionSets/x86/Implementation/PerformImplementation.hpp @@ -496,6 +496,41 @@ void mul(IntT &destination_high, IntT &destination_low, IntT source, Status &sta status.overflow = status.carry = destination_high; } +template +void imul(IntT &destination_high, IntT &destination_low, IntT source, Status &status) { + /* + (as modified by https://www.felixcloutier.com/x86/daa ...) + + IF (OperandSize = 8) + THEN + AX ← AL ∗ SRC (* signed multiplication *) + IF (AX = SignExtend(AL)) + THEN CF = 0; OF = 0; + ELSE CF = 1; OF = 1; + FI; + ELSE IF OperandSize = 16 + THEN + DX:AX ← AX ∗ SRC (* signed multiplication *) + IF (DX:AX = SignExtend(AX)) + THEN CF = 0; OF = 0; + ELSE CF = 1; OF = 1; + FI; + ELSE (* OperandSize = 32 *) + EDX:EAX ← EAX ∗ SRC (* signed multiplication *) + IF (EDX:EAX = SignExtend(EAX)) + THEN CF = 0; OF = 0; + ELSE CF = 1; OF = 1; + FI; + FI; + */ + using sIntT = typename std::make_signed::type; + destination_high = (sIntT(destination_low) * sIntT(source)) >> (8 * sizeof(IntT)); + destination_low = IntT(sIntT(destination_low) * sIntT(source)); + + const auto sign_extension = (destination_low & top_bit()) ? IntT(~0) : 0; + status.overflow = status.carry = destination_high != sign_extension; +} + template void and_(IntT &destination, IntT source, Status &status) { /* @@ -671,6 +706,15 @@ template < Primitive::mul(registers.edx(), registers.eax(), source(), status); } return; + case Operation::IMUL_1: + if constexpr (data_size == DataSize::Byte) { + Primitive::imul(registers.ah(), registers.al(), source(), status); + } else if constexpr (data_size == DataSize::Word) { + Primitive::imul(registers.dx(), registers.ax(), source(), status); + } else if constexpr (data_size == DataSize::DWord) { + Primitive::imul(registers.edx(), registers.eax(), source(), status); + } + return; case Operation::AND: Primitive::and_(destination(), source(), status); break; diff --git a/InstructionSets/x86/Instruction.hpp b/InstructionSets/x86/Instruction.hpp index 67a435cbb..d948fd88f 100644 --- a/InstructionSets/x86/Instruction.hpp +++ b/InstructionSets/x86/Instruction.hpp @@ -71,7 +71,7 @@ enum class Operation: uint8_t { SUB, /// Unsigned multiply; multiplies the source value by EAX, AX or AL, storing the result in EDX:EAX, DX:AX or AX. MUL, - /// Single operand signed multiply; multiplies the source value by AX or AL, storing the result in DX:AX or AX. + /// Single operand signed multiply; multiplies the source value by EAX, AX or AL, storing the result in EDX:EAX, DX:AX or AX. IMUL_1, /// Unsigned divide; divide the source value by AX or AL, storing the quotient in AL and the remainder in AH. DIV, diff --git a/OSBindings/Mac/Clock SignalTests/8088Tests.mm b/OSBindings/Mac/Clock SignalTests/8088Tests.mm index 1a741a5b1..d9bf0bdaa 100644 --- a/OSBindings/Mac/Clock SignalTests/8088Tests.mm +++ b/OSBindings/Mac/Clock SignalTests/8088Tests.mm @@ -278,56 +278,59 @@ struct FailedExecution { - (NSArray *)testFiles { NSString *path = [NSString stringWithUTF8String:TestSuiteHome]; NSSet *allowList = [NSSet setWithArray:@[ -// @"37.json.gz", // AAA -// @"3F.json.gz", // AAS -// @"D4.json.gz", // AAM -// @"D5.json.gz", // AAD -// @"27.json.gz", // DAA -// @"2F.json.gz", // DAS -// -// @"98.json.gz", // CBW -// @"99.json.gz", // CWD -// -// // ESC -// @"D8.json.gz", @"D9.json.gz", @"DA.json.gz", @"DB.json.gz", -// @"DC.json.gz", @"DD.json.gz", @"DE.json.gz", @"DE.json.gz", -// -// // Untested: HLT, WAIT -//// -// // ADC -// @"10.json.gz", @"11.json.gz", @"12.json.gz", @"13.json.gz", @"14.json.gz", @"15.json.gz", -// @"80.2.json.gz", @"81.2.json.gz", @"83.2.json.gz", -// -// // ADD -// @"00.json.gz", @"01.json.gz", @"02.json.gz", @"03.json.gz", @"04.json.gz", @"05.json.gz", -// @"80.0.json.gz", @"81.0.json.gz", @"83.0.json.gz", -// -// // SBB -// @"18.json.gz", @"19.json.gz", @"1A.json.gz", @"1B.json.gz", @"1C.json.gz", @"1D.json.gz", -// @"80.3.json.gz", @"81.3.json.gz", @"83.3.json.gz", -// -// // SUB -// @"28.json.gz", @"29.json.gz", @"2A.json.gz", @"2B.json.gz", @"2C.json.gz", @"2D.json.gz", -// @"80.5.json.gz", @"81.5.json.gz", @"83.5.json.gz", + @"37.json.gz", // AAA + @"3F.json.gz", // AAS + @"D4.json.gz", // AAM + @"D5.json.gz", // AAD + @"27.json.gz", // DAA + @"2F.json.gz", // DAS + + @"98.json.gz", // CBW + @"99.json.gz", // CWD + + // ESC + @"D8.json.gz", @"D9.json.gz", @"DA.json.gz", @"DB.json.gz", + @"DC.json.gz", @"DD.json.gz", @"DE.json.gz", @"DE.json.gz", + + // Untested: HLT, WAIT + + // ADC + @"10.json.gz", @"11.json.gz", @"12.json.gz", @"13.json.gz", @"14.json.gz", @"15.json.gz", + @"80.2.json.gz", @"81.2.json.gz", @"83.2.json.gz", + + // ADD + @"00.json.gz", @"01.json.gz", @"02.json.gz", @"03.json.gz", @"04.json.gz", @"05.json.gz", + @"80.0.json.gz", @"81.0.json.gz", @"83.0.json.gz", + + // SBB + @"18.json.gz", @"19.json.gz", @"1A.json.gz", @"1B.json.gz", @"1C.json.gz", @"1D.json.gz", + @"80.3.json.gz", @"81.3.json.gz", @"83.3.json.gz", + + // SUB + @"28.json.gz", @"29.json.gz", @"2A.json.gz", @"2B.json.gz", @"2C.json.gz", @"2D.json.gz", + @"80.5.json.gz", @"81.5.json.gz", @"83.5.json.gz", // MUL @"F6.4.json.gz", @"F7.4.json.gz", -// // NOP -// @"90.json.gz", + // IMUL_1 + @"F6.5.json.gz", @"F7.5.json.gz", + + // NOP + @"90.json.gz", // AND -// @"20.json.gz", @"21.json.gz", @"22.json.gz", @"23.json.gz", @"24.json.gz", @"25.json.gz", -// @"80.4.json.gz", @"81.4.json.gz", @"83.4.json.gz", -// -// // CALL -// @"E8.json.gz", @"FF.2.json.gz", -// @"9A.json.gz", @"FF.3.json.gz", -// -// @"F8.json.gz", // CLC -// @"FC.json.gz", // CLD -// @"FA.json.gz", // CLI -// @"F5.json.gz", // CMC + @"20.json.gz", @"21.json.gz", @"22.json.gz", @"23.json.gz", @"24.json.gz", @"25.json.gz", + @"80.4.json.gz", @"81.4.json.gz", @"83.4.json.gz", + + // CALL + @"E8.json.gz", @"FF.2.json.gz", + @"9A.json.gz", @"FF.3.json.gz", + + @"F8.json.gz", // CLC + @"FC.json.gz", // CLD + @"FA.json.gz", // CLI + @"F5.json.gz", // CMC ]]; NSSet *ignoreList = nil; @@ -585,7 +588,7 @@ struct FailedExecution { - (void)testDecoding { NSMutableArray *failures = [[NSMutableArray alloc] init]; - for(NSString *file in [self testFiles]) { + for(NSString *file in [self testFiles]) @autoreleasepool { for(NSDictionary *test in [self testsInFile:file]) { // A single failure per instruction is fine. if(![self applyDecodingTest:test file:file assert:YES]) { @@ -604,7 +607,7 @@ struct FailedExecution { - (void)testExecution { NSDictionary *metadata = [self metadata]; - for(NSString *file in [self testFiles]) { + for(NSString *file in [self testFiles]) @autoreleasepool { // Determine the metadata key. NSString *const name = [file lastPathComponent]; NSRange first_dot = [name rangeOfString:@"."]; From 04128909239040e951e502c853c54f917a9d8b20 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 9 Oct 2023 22:16:37 -0400 Subject: [PATCH 053/128] Add STC, STD, STI. --- .../x86/Implementation/PerformImplementation.hpp | 6 ++++++ InstructionSets/x86/Instruction.hpp | 2 +- OSBindings/Mac/Clock SignalTests/8088Tests.mm | 3 +++ 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/InstructionSets/x86/Implementation/PerformImplementation.hpp b/InstructionSets/x86/Implementation/PerformImplementation.hpp index dfbcfd72d..d1e4c0810 100644 --- a/InstructionSets/x86/Implementation/PerformImplementation.hpp +++ b/InstructionSets/x86/Implementation/PerformImplementation.hpp @@ -610,6 +610,9 @@ void cwd(IntT &dx, IntT ax) { inline void clc(Status &status) { status.carry = 0; } inline void cld(Status &status) { status.direction = 0; } inline void cli(Status &status) { status.interrupt = 0; } // TODO: quite a bit more in protected mode. +inline void stc(Status &status) { status.carry = 1; } +inline void std(Status &status) { status.direction = 1; } +inline void sti(Status &status) { status.interrupt = 1; } // TODO: quite a bit more in protected mode. inline void cmc(Status &status) { status.carry = !status.carry; } } @@ -731,6 +734,9 @@ template < 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; } diff --git a/InstructionSets/x86/Instruction.hpp b/InstructionSets/x86/Instruction.hpp index d948fd88f..9c93e53e2 100644 --- a/InstructionSets/x86/Instruction.hpp +++ b/InstructionSets/x86/Instruction.hpp @@ -195,7 +195,7 @@ enum class Operation: uint8_t { CLI, /// Set carry flag. STC, - /// Set decimal flag. + /// Set direction flag. STD, /// Set interrupt flag. STI, diff --git a/OSBindings/Mac/Clock SignalTests/8088Tests.mm b/OSBindings/Mac/Clock SignalTests/8088Tests.mm index d9bf0bdaa..c8d68a34b 100644 --- a/OSBindings/Mac/Clock SignalTests/8088Tests.mm +++ b/OSBindings/Mac/Clock SignalTests/8088Tests.mm @@ -330,6 +330,9 @@ struct FailedExecution { @"F8.json.gz", // CLC @"FC.json.gz", // CLD @"FA.json.gz", // CLI + @"F9.json.gz", // STC + @"FD.json.gz", // STD + @"FB.json.gz", // STI @"F5.json.gz", // CMC ]]; From b420d4cbd75fa43f2a23196d19c5bb0cde4137c0 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 9 Oct 2023 22:22:06 -0400 Subject: [PATCH 054/128] Collect TODOs. --- OSBindings/Mac/Clock SignalTests/8088Tests.mm | 39 ++++++++++++++++--- 1 file changed, 34 insertions(+), 5 deletions(-) diff --git a/OSBindings/Mac/Clock SignalTests/8088Tests.mm b/OSBindings/Mac/Clock SignalTests/8088Tests.mm index c8d68a34b..c08c7b2ae 100644 --- a/OSBindings/Mac/Clock SignalTests/8088Tests.mm +++ b/OSBindings/Mac/Clock SignalTests/8088Tests.mm @@ -316,16 +316,41 @@ struct FailedExecution { // IMUL_1 @"F6.5.json.gz", @"F7.5.json.gz", - // NOP - @"90.json.gz", + // TODO: DIV, IDIV + // TODO: INC, DEC + // TODO: IN, OUT + // TODO: JO, JNO, JB, JNB, JZ, JNZ, JBE, JNBE, JS, JNS, JP, JNP, JL, JNL, JLE, JNLE, + + // CALL + @"E8.json.gz", @"FF.2.json.gz", + @"9A.json.gz", @"FF.3.json.gz", + + // TODO: IRET + // TODO: RET + // TODO: JMP + // TODO: JCXZ + // TODO: INT, INTO + + // TODO: LAHF, SAHF + // TODO: LDS, LES + // TODO: LEA + + // TODO: CMPS, LODS, MOVS, SCAS, STOS + + // TODO: LOOP, LOOPE, LOOPNE + + // TODO: MOV + // TODO: NEG, NOT, OR, XOR // AND @"20.json.gz", @"21.json.gz", @"22.json.gz", @"23.json.gz", @"24.json.gz", @"25.json.gz", @"80.4.json.gz", @"81.4.json.gz", @"83.4.json.gz", - // CALL - @"E8.json.gz", @"FF.2.json.gz", - @"9A.json.gz", @"FF.3.json.gz", + // NOP + @"90.json.gz", + + // TODO: POP, POPF, PUSH, PUSHF + // TODO: RCL, RCR, ROL, ROR, SAL, SAR, SHR @"F8.json.gz", // CLC @"FC.json.gz", // CLD @@ -334,6 +359,10 @@ struct FailedExecution { @"FD.json.gz", // STD @"FB.json.gz", // STI @"F5.json.gz", // CMC + + // TODO: CMP, TEST + // TODO: XCHG, XLAT + // TODO: SALC, SETMO, SETMOC ]]; NSSet *ignoreList = nil; From dbf7d07609c4c3159eeebe23387e85e2cef3a61f Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 10 Oct 2023 10:34:18 -0400 Subject: [PATCH 055/128] Add DIV, faulty IDIV. --- .../Implementation/PerformImplementation.hpp | 59 ++++++++++++++++++- InstructionSets/x86/Instruction.hpp | 2 +- InstructionSets/x86/Interrupts.hpp | 10 ++-- OSBindings/Mac/Clock SignalTests/8088Tests.mm | 14 ++++- 4 files changed, 75 insertions(+), 10 deletions(-) diff --git a/InstructionSets/x86/Implementation/PerformImplementation.hpp b/InstructionSets/x86/Implementation/PerformImplementation.hpp index d1e4c0810..2f224fd4d 100644 --- a/InstructionSets/x86/Implementation/PerformImplementation.hpp +++ b/InstructionSets/x86/Implementation/PerformImplementation.hpp @@ -256,7 +256,7 @@ inline void aam(CPU::RegisterPair16 &ax, uint8_t imm, Status &status, FlowContro If ... an immediate value of 0 is used, it will cause a #DE (divide error) exception. */ if(!imm) { - flow_controller.interrupt(Interrupt::DivideByZero); + flow_controller.interrupt(Interrupt::DivideError); return; } @@ -531,6 +531,45 @@ void imul(IntT &destination_high, IntT &destination_low, IntT source, Status &st status.overflow = status.carry = destination_high != sign_extension; } +template +void div(IntT &destination_high, IntT &destination_low, IntT source, FlowControllerT &flow_controller) { + if(!source) { + flow_controller.interrupt(Interrupt::DivideError); + return; + } + + // TEMPORARY HACK. Will not work with DWords. + const uint32_t dividend = (destination_high << (8 * sizeof(IntT))) + destination_low; + const auto result = dividend / source; + if(IntT(result) != result) { + flow_controller.interrupt(Interrupt::DivideError); + return; + } + + destination_low = IntT(result); + destination_high = dividend % source; +} + +template +void idiv(IntT &destination_high, IntT &destination_low, IntT source, FlowControllerT &flow_controller) { + if(!source) { + flow_controller.interrupt(Interrupt::DivideError); + return; + } + + // TEMPORARY HACK. Will not work with DWords. + using sIntT = typename std::make_signed::type; + const int32_t dividend = (sIntT(destination_high) << (8 * sizeof(IntT))) + destination_low; + const auto result = dividend / sIntT(source); + if(sIntT(result) != result) { + flow_controller.interrupt(Interrupt::DivideError); + return; + } + + destination_low = IntT(result); + destination_high = dividend % sIntT(source); +} + template void and_(IntT &destination, IntT source, Status &status) { /* @@ -718,6 +757,24 @@ template < Primitive::imul(registers.edx(), registers.eax(), source(), status); } return; + case Operation::DIV: + if constexpr (data_size == DataSize::Byte) { + Primitive::div(registers.ah(), registers.al(), source(), flow_controller); + } else if constexpr (data_size == DataSize::Word) { + Primitive::div(registers.dx(), registers.ax(), source(), flow_controller); + } else if constexpr (data_size == DataSize::DWord) { + Primitive::div(registers.edx(), registers.eax(), source(), flow_controller); + } + return; + case Operation::IDIV: + if constexpr (data_size == DataSize::Byte) { + Primitive::idiv(registers.ah(), registers.al(), source(), flow_controller); + } else if constexpr (data_size == DataSize::Word) { + Primitive::idiv(registers.dx(), registers.ax(), source(), flow_controller); + } else if constexpr (data_size == DataSize::DWord) { + Primitive::idiv(registers.edx(), registers.eax(), source(), flow_controller); + } + return; case Operation::AND: Primitive::and_(destination(), source(), status); break; diff --git a/InstructionSets/x86/Instruction.hpp b/InstructionSets/x86/Instruction.hpp index 9c93e53e2..4900a42b7 100644 --- a/InstructionSets/x86/Instruction.hpp +++ b/InstructionSets/x86/Instruction.hpp @@ -73,7 +73,7 @@ enum class Operation: uint8_t { MUL, /// Single operand signed multiply; multiplies the source value by EAX, AX or AL, storing the result in EDX:EAX, DX:AX or AX. IMUL_1, - /// Unsigned divide; divide the source value by AX or AL, storing the quotient in AL and the remainder in AH. + /// Unsigned divide; divide the AX, DX:AX or EDX:AX by the source(), storing the quotient in AL, AX or EAX and the remainder in AH, DX or EDX. DIV, /// Signed divide; divide the source value by AX or AL, storing the quotient in AL and the remainder in AH. IDIV, diff --git a/InstructionSets/x86/Interrupts.hpp b/InstructionSets/x86/Interrupts.hpp index c8d1dcb68..51366f11d 100644 --- a/InstructionSets/x86/Interrupts.hpp +++ b/InstructionSets/x86/Interrupts.hpp @@ -12,11 +12,11 @@ namespace InstructionSet::x86 { enum Interrupt { - DivideByZero = 0, - SingleStep = 1, - NMI = 2, - OneByte = 3, - OnOverflow = 4, + DivideError = 0, + SingleStep = 1, + NMI = 2, + OneByte = 3, + OnOverflow = 4, }; } diff --git a/OSBindings/Mac/Clock SignalTests/8088Tests.mm b/OSBindings/Mac/Clock SignalTests/8088Tests.mm index c08c7b2ae..0fdfd263c 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 { @@ -316,7 +316,12 @@ struct FailedExecution { // IMUL_1 @"F6.5.json.gz", @"F7.5.json.gz", - // TODO: DIV, IDIV + // DIV + @"F6.6.json.gz", @"F7.6.json.gz", + + // IDIV + @"F6.7.json.gz", @"F7.7.json.gz", + // TODO: INC, DEC // TODO: IN, OUT // TODO: JO, JNO, JB, JNB, JZ, JNZ, JBE, JNBE, JS, JNS, JP, JNP, JL, JNL, JLE, JNLE, @@ -363,6 +368,7 @@ struct FailedExecution { // TODO: CMP, TEST // TODO: XCHG, XLAT // TODO: SALC, SETMO, SETMOC + ]]; NSSet *ignoreList = nil; @@ -537,8 +543,10 @@ struct FailedExecution { for(NSArray *ram in final_state[@"ram"]) { execution_support.memory.touch([ram[0] intValue]); } - [self populate:execution_support.registers status:initial_status value:initial_state[@"regs"]]; + Registers initial_registers; + [self populate:initial_registers status:initial_status value:initial_state[@"regs"]]; execution_support.status = initial_status; + execution_support.registers = initial_registers; // Execute instruction. execution_support.registers.ip_ += decoded.first; From 3d08953103288022ffe821a885def421309d41cb Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 10 Oct 2023 12:43:41 -0400 Subject: [PATCH 056/128] Add TODO. --- InstructionSets/x86/Instruction.hpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/InstructionSets/x86/Instruction.hpp b/InstructionSets/x86/Instruction.hpp index 4900a42b7..9bbc80dc4 100644 --- a/InstructionSets/x86/Instruction.hpp +++ b/InstructionSets/x86/Instruction.hpp @@ -479,6 +479,10 @@ constexpr bool supports(Operation operation, Repetition repetition) { case Operation::STOS: return true; + // TODO: my new understanding is that the 8086 and 8088 recognise rep and repne on + // IDIV — and possibly DIV — as a quirk, affecting the outcome (possibly negativing the result?). + // So the test below should be a function of model, if I come to a conclusion about whether I'm + // going for fidelity to the instruction set as generally implemented, or to Intel's specific implementation. case Operation::IDIV: return repetition == Repetition::RepNE; } From f083eab0116e758fcbfafe671c608ca4b7e9b358 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 10 Oct 2023 15:57:33 -0400 Subject: [PATCH 057/128] Implement INC, DEC. --- .../Implementation/PerformImplementation.hpp | 112 ++++++++++++++++++ OSBindings/Mac/Clock SignalTests/8088Tests.mm | 22 +++- 2 files changed, 129 insertions(+), 5 deletions(-) diff --git a/InstructionSets/x86/Implementation/PerformImplementation.hpp b/InstructionSets/x86/Implementation/PerformImplementation.hpp index 2f224fd4d..ba58f397d 100644 --- a/InstructionSets/x86/Implementation/PerformImplementation.hpp +++ b/InstructionSets/x86/Implementation/PerformImplementation.hpp @@ -533,6 +533,43 @@ void imul(IntT &destination_high, IntT &destination_low, IntT source, Status &st template void div(IntT &destination_high, IntT &destination_low, IntT source, FlowControllerT &flow_controller) { + /* + IF SRC = 0 + THEN #DE; (* divide error *) + FI; + IF OperandSize = 8 (* word/byte operation *) + THEN + temp ← AX / SRC; + IF temp > FFH + THEN #DE; (* divide error *) ; + ELSE + AL ← temp; + AH ← AX MOD SRC; + FI; + ELSE + IF OperandSize = 16 (* doubleword/word operation *) + THEN + temp ← DX:AX / SRC; + IF temp > FFFFH + THEN #DE; (* divide error *) ; + ELSE + AX ← temp; + DX ← DX:AX MOD SRC; + FI; + ELSE (* quadword/doubleword operation *) + temp ← EDX:EAX / SRC; + IF temp > FFFFFFFFH + THEN #DE; (* divide error *) ; + ELSE + EAX ← temp; + EDX ← EDX:EAX MOD SRC; + FI; + FI; + FI; + */ + /* + The CF, OF, SF, ZF, AF, and PF flags are undefined. + */ if(!source) { flow_controller.interrupt(Interrupt::DivideError); return; @@ -552,6 +589,43 @@ void div(IntT &destination_high, IntT &destination_low, IntT source, FlowControl template void idiv(IntT &destination_high, IntT &destination_low, IntT source, FlowControllerT &flow_controller) { + /* + IF SRC = 0 + THEN #DE; (* divide error *) + FI; + IF OperandSize = 8 (* word/byte operation *) + THEN + temp ← AX / SRC; (* signed division *) + IF (temp > 7FH) OR (temp < 80H) (* if a positive result is greater than 7FH or a negative result is less than 80H *) + THEN #DE; (* divide error *) ; + ELSE + AL ← temp; + AH ← AX MOD SRC; + FI; + ELSE + IF OperandSize = 16 (* doubleword/word operation *) + THEN + temp ← DX:AX / SRC; (* signed division *) + IF (temp > 7FFFH) OR (temp < 8000H) (* if a positive result is greater than 7FFFH or a negative result is less than 8000H *) + THEN #DE; (* divide error *) ; + ELSE + AX ← temp; + DX ← DX:AX MOD SRC; + FI; + ELSE (* quadword/doubleword operation *) + temp ← EDX:EAX / SRC; (* signed division *) + IF (temp > 7FFFFFFFH) OR (temp < 80000000H) (* if a positive result is greater than 7FFFFFFFH or a negative result is less than 80000000H *) + THEN #DE; (* divide error *) ; + ELSE + EAX ← temp; + EDX ← EDX:EAX MOD SRC; + FI; + FI; + FI; + */ + /* + The CF, OF, SF, ZF, AF, and PF flags are undefined. + */ if(!source) { flow_controller.interrupt(Interrupt::DivideError); return; @@ -570,6 +644,41 @@ void idiv(IntT &destination_high, IntT &destination_low, IntT source, FlowContro destination_high = dividend % sIntT(source); } +template +void inc(IntT &destination, Status &status) { + /* + DEST ← DEST + 1; + */ + /* + The CF flag is not affected. + The OF, SF, ZF, AF, and PF flags are set according to the result. + */ + ++destination; + + status.overflow = destination == top_bit(); + status.sign = destination & top_bit(); + status.zero = status.parity = destination; + status.auxiliary_carry = ((destination - 1) ^ destination) & 0x10; +} + +template +void dec(IntT &destination, Status &status) { + /* + DEST ← DEST - 1; + */ + /* + The CF flag is not affected. + The OF, SF, ZF, AF, and PF flags are set according to the result. + */ + status.overflow = destination == top_bit(); + + --destination; + + status.sign = destination & top_bit(); + status.zero = status.parity = destination; + status.auxiliary_carry = ((destination + 1) ^ destination) & 0x10; +} + template void and_(IntT &destination, IntT source, Status &status) { /* @@ -776,6 +885,9 @@ template < } return; + case Operation::INC: Primitive::inc(destination(), status); break; + case Operation::DEC: Primitive::dec(destination(), status); break; + case Operation::AND: Primitive::and_(destination(), source(), status); break; case Operation::CALLrel: diff --git a/OSBindings/Mac/Clock SignalTests/8088Tests.mm b/OSBindings/Mac/Clock SignalTests/8088Tests.mm index 0fdfd263c..33da0b107 100644 --- a/OSBindings/Mac/Clock SignalTests/8088Tests.mm +++ b/OSBindings/Mac/Clock SignalTests/8088Tests.mm @@ -278,7 +278,7 @@ struct FailedExecution { - (NSArray *)testFiles { NSString *path = [NSString stringWithUTF8String:TestSuiteHome]; NSSet *allowList = [NSSet setWithArray:@[ - @"37.json.gz", // AAA +/* @"37.json.gz", // AAA @"3F.json.gz", // AAS @"D4.json.gz", // AAM @"D5.json.gz", // AAD @@ -320,14 +320,25 @@ struct FailedExecution { @"F6.6.json.gz", @"F7.6.json.gz", // IDIV - @"F6.7.json.gz", @"F7.7.json.gz", + @"F6.7.json.gz", @"F7.7.json.gz",*/ + + // INC + @"40.json.gz", @"41.json.gz", @"42.json.gz", @"43.json.gz", + @"44.json.gz", @"45.json.gz", @"46.json.gz", @"47.json.gz", + @"FE.0.json.gz", + @"FF.0.json.gz", + + // DEC + @"48.json.gz", @"49.json.gz", @"4A.json.gz", @"4B.json.gz", + @"4C.json.gz", @"4D.json.gz", @"4E.json.gz", @"4F.json.gz", + @"FE.1.json.gz", + @"FF.1.json.gz", - // TODO: INC, DEC // TODO: IN, OUT // TODO: JO, JNO, JB, JNB, JZ, JNZ, JBE, JNBE, JS, JNS, JP, JNP, JL, JNL, JLE, JNLE, // CALL - @"E8.json.gz", @"FF.2.json.gz", +/* @"E8.json.gz", @"FF.2.json.gz", @"9A.json.gz", @"FF.3.json.gz", // TODO: IRET @@ -364,11 +375,12 @@ struct FailedExecution { @"FD.json.gz", // STD @"FB.json.gz", // STI @"F5.json.gz", // CMC +*/ // TODO: CMP, TEST // TODO: XCHG, XLAT // TODO: SALC, SETMO, SETMOC - + ]]; NSSet *ignoreList = nil; From ec982444f74b264109f94ce3be4ec4558492a415 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 10 Oct 2023 16:14:20 -0400 Subject: [PATCH 058/128] Add getters to obscure internal flag storage. --- InstructionSets/x86/Status.hpp | 49 +++++++++++++++++++ OSBindings/Mac/Clock SignalTests/8088Tests.mm | 1 + 2 files changed, 50 insertions(+) diff --git a/InstructionSets/x86/Status.hpp b/InstructionSets/x86/Status.hpp index 81e229335..a678f1603 100644 --- a/InstructionSets/x86/Status.hpp +++ b/InstructionSets/x86/Status.hpp @@ -70,6 +70,55 @@ struct Status { uint32_t parity; // Flag getters. + enum class Flag { + Carry, + AuxiliaryCarry, + Sign, + Overflow, + Trap, + Interrupt, + Direction, + Zero, + ParityOdd + }; + template bool flag() { + switch(flag) { + case Flag::Carry: return carry; + case Flag::AuxiliaryCarry: return auxiliary_carry; + case Flag::Sign: return sign; + case Flag::Overflow: return overflow; + case Flag::Trap: return trap; + case Flag::Interrupt: return interrupt; + case Flag::Direction: return direction; + case Flag::Zero: return !zero; + case Flag::ParityOdd: return not_parity_bit(); + } + } + + // Condition evaluation. + enum class Condition { + Overflow, + Below, + Zero, + BelowOrEqual, + Sign, + ParityOdd, + Less, + LessOrEqual + }; + template bool condition() { + switch(test) { + case Condition::Overflow: return flag(); + case Condition::Below: return flag(); + case Condition::Zero: return flag(); + case Condition::BelowOrEqual: return flag() || flag(); + case Condition::Sign: return flag(); + case Condition::ParityOdd: return flag(); + case Condition::Less: return flag() != flag(); + case Condition::LessOrEqual: return flag() || flag() != flag(); + } + } + template IntT carry_bit() const { return carry ? 1 : 0; } bool not_parity_bit() const { // x86 parity always considers the lowest 8-bits only. diff --git a/OSBindings/Mac/Clock SignalTests/8088Tests.mm b/OSBindings/Mac/Clock SignalTests/8088Tests.mm index 33da0b107..e0f929e15 100644 --- a/OSBindings/Mac/Clock SignalTests/8088Tests.mm +++ b/OSBindings/Mac/Clock SignalTests/8088Tests.mm @@ -335,6 +335,7 @@ struct FailedExecution { @"FF.1.json.gz", // TODO: IN, OUT + // TODO: JO, JNO, JB, JNB, JZ, JNZ, JBE, JNBE, JS, JNS, JP, JNP, JL, JNL, JLE, JNLE, // CALL From de95026076ee0ae345d6abbf56b158b45009a0c7 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 10 Oct 2023 16:27:06 -0400 Subject: [PATCH 059/128] Implement Jcc. --- .../Implementation/PerformImplementation.hpp | 132 ++++++++++++++++++ OSBindings/Mac/Clock SignalTests/8088Tests.mm | 37 +++-- 2 files changed, 160 insertions(+), 9 deletions(-) diff --git a/InstructionSets/x86/Implementation/PerformImplementation.hpp b/InstructionSets/x86/Implementation/PerformImplementation.hpp index ba58f397d..2643aab21 100644 --- a/InstructionSets/x86/Implementation/PerformImplementation.hpp +++ b/InstructionSets/x86/Implementation/PerformImplementation.hpp @@ -661,6 +661,25 @@ void inc(IntT &destination, Status &status) { status.auxiliary_carry = ((destination - 1) ^ destination) & 0x10; } +template +inline void jump(bool condition, IntT displacement, RegistersT ®isters, FlowControllerT &flow_controller) { + /* + IF condition + THEN + EIP ← EIP + SignExtend(DEST); + IF OperandSize = 16 + THEN + EIP ← EIP AND 0000FFFFH; + FI; + FI; + */ + + // TODO: proper behaviour in 32-bit. + if(condition) { + flow_controller.jump(registers.ip() + displacement); + } +} + template void dec(IntT &destination, Status &status) { /* @@ -900,6 +919,119 @@ template < Primitive::call_far(instruction, flow_controller, registers, memory); return; + case Operation::JO: + Primitive::jump( + status.condition(), + instruction.displacement(), + registers, + flow_controller); + break; + case Operation::JNO: + Primitive::jump( + !status.condition(), + instruction.displacement(), + registers, + flow_controller); + break; + case Operation::JB: + Primitive::jump( + status.condition(), + instruction.displacement(), + registers, + flow_controller); + break; + case Operation::JNB: + Primitive::jump( + !status.condition(), + instruction.displacement(), + registers, + flow_controller); + break; + case Operation::JZ: + Primitive::jump( + status.condition(), + instruction.displacement(), + registers, + flow_controller); + break; + case Operation::JNZ: + Primitive::jump( + !status.condition(), + instruction.displacement(), + registers, + flow_controller); + break; + case Operation::JBE: + Primitive::jump( + status.condition(), + instruction.displacement(), + registers, + flow_controller); + break; + case Operation::JNBE: + Primitive::jump( + !status.condition(), + instruction.displacement(), + registers, + flow_controller); + break; + case Operation::JS: + Primitive::jump( + status.condition(), + instruction.displacement(), + registers, + flow_controller); + break; + case Operation::JNS: + Primitive::jump( + !status.condition(), + instruction.displacement(), + registers, + flow_controller); + break; + case Operation::JP: + Primitive::jump( + !status.condition(), + instruction.displacement(), + registers, + flow_controller); + break; + case Operation::JNP: + Primitive::jump( + status.condition(), + instruction.displacement(), + registers, + flow_controller); + break; + case Operation::JL: + Primitive::jump( + status.condition(), + instruction.displacement(), + registers, + flow_controller); + break; + case Operation::JNL: + Primitive::jump( + !status.condition(), + instruction.displacement(), + registers, + flow_controller); + break; + case Operation::JLE: + Primitive::jump( + status.condition(), + instruction.displacement(), + registers, + flow_controller); + break; + case Operation::JNLE: + Primitive::jump( + !status.condition(), + instruction.displacement(), + registers, + flow_controller); + break; + case Operation::CLC: Primitive::clc(status); return; case Operation::CLD: Primitive::cld(status); return; case Operation::CLI: Primitive::cli(status); return; diff --git a/OSBindings/Mac/Clock SignalTests/8088Tests.mm b/OSBindings/Mac/Clock SignalTests/8088Tests.mm index e0f929e15..0c3134854 100644 --- a/OSBindings/Mac/Clock SignalTests/8088Tests.mm +++ b/OSBindings/Mac/Clock SignalTests/8088Tests.mm @@ -220,6 +220,10 @@ class FlowController { registers_.ip_ = offset; } + void jump(uint16_t address) { + registers_.ip_ = address; + } + void halt() {} void wait() {} @@ -323,20 +327,35 @@ struct FailedExecution { @"F6.7.json.gz", @"F7.7.json.gz",*/ // INC - @"40.json.gz", @"41.json.gz", @"42.json.gz", @"43.json.gz", - @"44.json.gz", @"45.json.gz", @"46.json.gz", @"47.json.gz", - @"FE.0.json.gz", - @"FF.0.json.gz", +// @"40.json.gz", @"41.json.gz", @"42.json.gz", @"43.json.gz", +// @"44.json.gz", @"45.json.gz", @"46.json.gz", @"47.json.gz", +// @"FE.0.json.gz", +// @"FF.0.json.gz", // DEC - @"48.json.gz", @"49.json.gz", @"4A.json.gz", @"4B.json.gz", - @"4C.json.gz", @"4D.json.gz", @"4E.json.gz", @"4F.json.gz", - @"FE.1.json.gz", - @"FF.1.json.gz", +// @"48.json.gz", @"49.json.gz", @"4A.json.gz", @"4B.json.gz", +// @"4C.json.gz", @"4D.json.gz", @"4E.json.gz", @"4F.json.gz", +// @"FE.1.json.gz", +// @"FF.1.json.gz", // TODO: IN, OUT - // TODO: JO, JNO, JB, JNB, JZ, JNZ, JBE, JNBE, JS, JNS, JP, JNP, JL, JNL, JLE, JNLE, + @"70.json.gz", // JO + @"71.json.gz", // JNO + @"72.json.gz", // JB + @"73.json.gz", // JNB + @"74.json.gz", // JZ + @"75.json.gz", // JNZ + @"76.json.gz", // JBE + @"77.json.gz", // JNBE + @"78.json.gz", // JS + @"79.json.gz", // JNS + @"7A.json.gz", // JP + @"7B.json.gz", // JNP + @"7C.json.gz", // JL + @"7D.json.gz", // JNL + @"7E.json.gz", // JLE + @"7F.json.gz", // JNLE // CALL /* @"E8.json.gz", @"FF.2.json.gz", From 0ecc319ee65d87af6e8bc4981c04e19d801ef47b Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 10 Oct 2023 17:12:06 -0400 Subject: [PATCH 060/128] Add OR, XOR. --- .../Implementation/PerformImplementation.hpp | 36 ++++++++++++++++ OSBindings/Mac/Clock SignalTests/8088Tests.mm | 43 +++++++++++-------- 2 files changed, 62 insertions(+), 17 deletions(-) diff --git a/InstructionSets/x86/Implementation/PerformImplementation.hpp b/InstructionSets/x86/Implementation/PerformImplementation.hpp index 2643aab21..dfee72060 100644 --- a/InstructionSets/x86/Implementation/PerformImplementation.hpp +++ b/InstructionSets/x86/Implementation/PerformImplementation.hpp @@ -715,6 +715,40 @@ void and_(IntT &destination, IntT source, Status &status) { status.zero = status.parity = destination; } +template +void or_(IntT &destination, IntT source, Status &status) { + /* + DEST ← DEST OR SRC; + */ + /* + The OF and CF flags are cleared; the SF, ZF, and PF flags are set according to the result. + The state of the AF flag is undefined. + */ + destination |= source; + + status.overflow = 0; + status.carry = 0; + status.sign = destination & top_bit(); + status.zero = status.parity = destination; +} + +template +void xor_(IntT &destination, IntT source, Status &status) { + /* + DEST ← DEST XOR SRC; + */ + /* + The OF and CF flags are cleared; the SF, ZF, and PF flags are set according to the result. + The state of the AF flag is undefined. + */ + destination ^= source; + + status.overflow = 0; + status.carry = 0; + status.sign = destination & top_bit(); + status.zero = status.parity = destination; +} + template inline void call_relative(IntT offset, RegistersT ®isters, FlowControllerT &flow_controller) { flow_controller.call(registers.ip() + offset); @@ -908,6 +942,8 @@ template < case Operation::DEC: Primitive::dec(destination(), status); break; case Operation::AND: Primitive::and_(destination(), source(), status); break; + case Operation::OR: Primitive::or_(destination(), source(), status); break; + case Operation::XOR: Primitive::xor_(destination(), source(), status); break; case Operation::CALLrel: Primitive::call_relative(instruction.displacement(), registers, flow_controller); diff --git a/OSBindings/Mac/Clock SignalTests/8088Tests.mm b/OSBindings/Mac/Clock SignalTests/8088Tests.mm index 0c3134854..6c166c4fb 100644 --- a/OSBindings/Mac/Clock SignalTests/8088Tests.mm +++ b/OSBindings/Mac/Clock SignalTests/8088Tests.mm @@ -340,22 +340,22 @@ struct FailedExecution { // TODO: IN, OUT - @"70.json.gz", // JO - @"71.json.gz", // JNO - @"72.json.gz", // JB - @"73.json.gz", // JNB - @"74.json.gz", // JZ - @"75.json.gz", // JNZ - @"76.json.gz", // JBE - @"77.json.gz", // JNBE - @"78.json.gz", // JS - @"79.json.gz", // JNS - @"7A.json.gz", // JP - @"7B.json.gz", // JNP - @"7C.json.gz", // JL - @"7D.json.gz", // JNL - @"7E.json.gz", // JLE - @"7F.json.gz", // JNLE +// @"70.json.gz", // JO +// @"71.json.gz", // JNO +// @"72.json.gz", // JB +// @"73.json.gz", // JNB +// @"74.json.gz", // JZ +// @"75.json.gz", // JNZ +// @"76.json.gz", // JBE +// @"77.json.gz", // JNBE +// @"78.json.gz", // JS +// @"79.json.gz", // JNS +// @"7A.json.gz", // JP +// @"7B.json.gz", // JNP +// @"7C.json.gz", // JL +// @"7D.json.gz", // JNL +// @"7E.json.gz", // JLE +// @"7F.json.gz", // JNLE // CALL /* @"E8.json.gz", @"FF.2.json.gz", @@ -376,12 +376,21 @@ struct FailedExecution { // TODO: LOOP, LOOPE, LOOPNE // TODO: MOV - // TODO: NEG, NOT, OR, XOR + // TODO: NEG, NOT // AND @"20.json.gz", @"21.json.gz", @"22.json.gz", @"23.json.gz", @"24.json.gz", @"25.json.gz", @"80.4.json.gz", @"81.4.json.gz", @"83.4.json.gz", +*/ + // OR + @"08.json.gz", @"09.json.gz", @"0A.json.gz", @"0B.json.gz", @"0C.json.gz", @"0D.json.gz", + @"80.1.json.gz", @"81.1.json.gz", @"83.1.json.gz", + // XOR + @"30.json.gz", @"31.json.gz", @"32.json.gz", @"33.json.gz", @"34.json.gz", @"35.json.gz", + @"80.6.json.gz", @"81.6.json.gz", @"83.6.json.gz", + +/* // NOP @"90.json.gz", From d0a9b5cb81db93cb0d36710dab7d9bbdf1d52e8b Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 10 Oct 2023 22:09:10 -0400 Subject: [PATCH 061/128] Implement NEG, NOT. --- .../Implementation/PerformImplementation.hpp | 36 +++++++++++++++++++ InstructionSets/x86/Instruction.hpp | 4 +-- OSBindings/Mac/Clock SignalTests/8088Tests.mm | 10 ++++-- 3 files changed, 46 insertions(+), 4 deletions(-) diff --git a/InstructionSets/x86/Implementation/PerformImplementation.hpp b/InstructionSets/x86/Implementation/PerformImplementation.hpp index dfee72060..55f5582b5 100644 --- a/InstructionSets/x86/Implementation/PerformImplementation.hpp +++ b/InstructionSets/x86/Implementation/PerformImplementation.hpp @@ -749,6 +749,40 @@ void xor_(IntT &destination, IntT source, Status &status) { status.zero = status.parity = destination; } +template +void neg(IntT &destination, Status &status) { + /* + IF DEST = 0 + THEN CF ← 0 + ELSE CF ← 1; + FI; + DEST ← –(DEST) + */ + /* + The CF flag cleared to 0 if the source operand is 0; otherwise it is set to 1. + The OF, SF, ZF, AF, and PF flags are set according to the result. + */ + status.auxiliary_carry = Numeric::carried_in<4>(IntT(0), destination, IntT(-destination)); + + destination = -destination; + + status.carry = destination; + status.overflow = destination == top_bit(); + status.sign = destination & top_bit(); + status.zero = status.parity = destination; +} + +template +void not_(IntT &destination) { + /* + DEST ← NOT DEST; + */ + /* + Flags affected: none. + */ + destination = ~destination; +} + template inline void call_relative(IntT offset, RegistersT ®isters, FlowControllerT &flow_controller) { flow_controller.call(registers.ip() + offset); @@ -944,6 +978,8 @@ template < case Operation::AND: Primitive::and_(destination(), source(), status); break; case Operation::OR: Primitive::or_(destination(), source(), status); break; case Operation::XOR: Primitive::xor_(destination(), source(), status); break; + case Operation::NEG: Primitive::neg(source(), status); break; + case Operation::NOT: Primitive::not_(source()); break; case Operation::CALLrel: Primitive::call_relative(instruction.displacement(), registers, flow_controller); diff --git a/InstructionSets/x86/Instruction.hpp b/InstructionSets/x86/Instruction.hpp index 9bbc80dc4..3cd6f756f 100644 --- a/InstructionSets/x86/Instruction.hpp +++ b/InstructionSets/x86/Instruction.hpp @@ -144,9 +144,9 @@ enum class Operation: uint8_t { /// Loads the destination with the source. MOV, - /// Negatives; source and destination point to the same thing, to negative. + /// Negatives; source indicates what to negative. NEG, - /// Logical NOT; source and destination point to the same thing, to negative. + /// Logical NOT; source indicates what to negative. NOT, /// Logical AND; source, destination, operand and displacement will be populated appropriately. AND, diff --git a/OSBindings/Mac/Clock SignalTests/8088Tests.mm b/OSBindings/Mac/Clock SignalTests/8088Tests.mm index 6c166c4fb..a14a11adb 100644 --- a/OSBindings/Mac/Clock SignalTests/8088Tests.mm +++ b/OSBindings/Mac/Clock SignalTests/8088Tests.mm @@ -376,12 +376,11 @@ struct FailedExecution { // TODO: LOOP, LOOPE, LOOPNE // TODO: MOV - // TODO: NEG, NOT // AND @"20.json.gz", @"21.json.gz", @"22.json.gz", @"23.json.gz", @"24.json.gz", @"25.json.gz", @"80.4.json.gz", @"81.4.json.gz", @"83.4.json.gz", -*/ + // OR @"08.json.gz", @"09.json.gz", @"0A.json.gz", @"0B.json.gz", @"0C.json.gz", @"0D.json.gz", @"80.1.json.gz", @"81.1.json.gz", @"83.1.json.gz", @@ -389,6 +388,13 @@ struct FailedExecution { // XOR @"30.json.gz", @"31.json.gz", @"32.json.gz", @"33.json.gz", @"34.json.gz", @"35.json.gz", @"80.6.json.gz", @"81.6.json.gz", @"83.6.json.gz", +*/ + + // NEG + @"F6.3.json.gz", @"F7.3.json.gz", + + // NOT + @"F6.2.json.gz", @"F7.2.json.gz", /* // NOP From 08867f497019c39974e214f7e9450f3d828c1522 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 10 Oct 2023 22:15:33 -0400 Subject: [PATCH 062/128] Implement CMP. --- .../Implementation/PerformImplementation.hpp | 17 +++++++++++------ OSBindings/Mac/Clock SignalTests/8088Tests.mm | 15 ++++++++++----- 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/InstructionSets/x86/Implementation/PerformImplementation.hpp b/InstructionSets/x86/Implementation/PerformImplementation.hpp index 55f5582b5..8cedf3767 100644 --- a/InstructionSets/x86/Implementation/PerformImplementation.hpp +++ b/InstructionSets/x86/Implementation/PerformImplementation.hpp @@ -455,7 +455,7 @@ void sbb(IntT &destination, IntT source, Status &status) { destination = result; } -template +template void sub(IntT &destination, IntT source, Status &status) { /* DEST ← DEST - SRC; @@ -471,7 +471,9 @@ void sub(IntT &destination, IntT source, Status &status) { status.zero = status.parity = result; status.overflow = overflow(destination, source, result); - destination = result; + if constexpr (write_back) { + destination = result; + } } template @@ -931,10 +933,13 @@ template < case Operation::HLT: flow_controller.halt(); return; case Operation::WAIT: flow_controller.wait(); return; - case Operation::ADC: Primitive::adc(destination(), source(), status); break; - case Operation::ADD: Primitive::add(destination(), source(), status); break; - case Operation::SBB: Primitive::sbb(destination(), source(), status); break; - case Operation::SUB: Primitive::sub(destination(), source(), status); break; + case Operation::ADC: Primitive::adc(destination(), source(), status); break; + case Operation::ADD: Primitive::add(destination(), source(), status); break; + case Operation::SBB: Primitive::sbb(destination(), source(), status); break; + case Operation::SUB: Primitive::sub(destination(), source(), status); break; + case Operation::CMP: Primitive::sub(destination(), source(), status); break; + + // TODO: all the below could call a common registers getter? case Operation::MUL: if constexpr (data_size == DataSize::Byte) { Primitive::mul(registers.ah(), registers.al(), source(), status); diff --git a/OSBindings/Mac/Clock SignalTests/8088Tests.mm b/OSBindings/Mac/Clock SignalTests/8088Tests.mm index a14a11adb..095bb4dbf 100644 --- a/OSBindings/Mac/Clock SignalTests/8088Tests.mm +++ b/OSBindings/Mac/Clock SignalTests/8088Tests.mm @@ -390,11 +390,11 @@ struct FailedExecution { @"80.6.json.gz", @"81.6.json.gz", @"83.6.json.gz", */ - // NEG - @"F6.3.json.gz", @"F7.3.json.gz", - - // NOT - @"F6.2.json.gz", @"F7.2.json.gz", +// // NEG +// @"F6.3.json.gz", @"F7.3.json.gz", +// +// // NOT +// @"F6.2.json.gz", @"F7.2.json.gz", /* // NOP @@ -412,6 +412,11 @@ struct FailedExecution { @"F5.json.gz", // CMC */ + // CMP + @"38.json.gz", @"39.json.gz", @"3A.json.gz", + @"3B.json.gz", @"3C.json.gz", @"3D.json.gz", + @"80.7.json.gz", @"81.7.json.gz", @"83.7.json.gz", + // TODO: CMP, TEST // TODO: XCHG, XLAT // TODO: SALC, SETMO, SETMOC From 51259070488ee4488bce87bc78abb3d8696ed82c Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 10 Oct 2023 22:28:10 -0400 Subject: [PATCH 063/128] Implement TEST. --- .../Implementation/PerformImplementation.hpp | 27 +++++++++++++++++++ OSBindings/Mac/Clock SignalTests/8088Tests.mm | 12 ++++++--- 2 files changed, 35 insertions(+), 4 deletions(-) diff --git a/InstructionSets/x86/Implementation/PerformImplementation.hpp b/InstructionSets/x86/Implementation/PerformImplementation.hpp index 8cedf3767..93a1ab80f 100644 --- a/InstructionSets/x86/Implementation/PerformImplementation.hpp +++ b/InstructionSets/x86/Implementation/PerformImplementation.hpp @@ -476,6 +476,32 @@ void sub(IntT &destination, IntT source, Status &status) { } } +template +void test(IntT &destination, IntT source, Status &status) { + /* + TEMP ← SRC1 AND SRC2; + SF ← MSB(TEMP); + IF TEMP = 0 + THEN ZF ← 0; + ELSE ZF ← 1; + FI: + PF ← BitwiseXNOR(TEMP[0:7]); + CF ← 0; + OF ← 0; + */ + /* + The OF and CF flags are cleared to 0. + The SF, ZF, and PF flags are set according to the result (see the “Operation” section above). + The state of the AF flag is undefined. + */ + const IntT result = destination & source; + + status.sign = result & top_bit(); + status.zero = result; + status.carry = status.overflow = 0; + status.parity = result; +} + template void mul(IntT &destination_high, IntT &destination_low, IntT source, Status &status) { /* @@ -938,6 +964,7 @@ template < case Operation::SBB: Primitive::sbb(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; // TODO: all the below could call a common registers getter? case Operation::MUL: diff --git a/OSBindings/Mac/Clock SignalTests/8088Tests.mm b/OSBindings/Mac/Clock SignalTests/8088Tests.mm index 095bb4dbf..8fe9cb536 100644 --- a/OSBindings/Mac/Clock SignalTests/8088Tests.mm +++ b/OSBindings/Mac/Clock SignalTests/8088Tests.mm @@ -413,11 +413,15 @@ struct FailedExecution { */ // CMP - @"38.json.gz", @"39.json.gz", @"3A.json.gz", - @"3B.json.gz", @"3C.json.gz", @"3D.json.gz", - @"80.7.json.gz", @"81.7.json.gz", @"83.7.json.gz", +// @"38.json.gz", @"39.json.gz", @"3A.json.gz", +// @"3B.json.gz", @"3C.json.gz", @"3D.json.gz", +// @"80.7.json.gz", @"81.7.json.gz", @"83.7.json.gz", + + // TEST + @"84.json.gz", @"85.json.gz", + @"A8.json.gz", @"A9.json.gz", + @"F6.0.json.gz", @"F7.0.json.gz", - // TODO: CMP, TEST // TODO: XCHG, XLAT // TODO: SALC, SETMO, SETMOC From a83b43a1aed3f3312428870ae6731fb5d5f565e2 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 10 Oct 2023 22:34:42 -0400 Subject: [PATCH 064/128] Implement XCHG. --- .../Implementation/PerformImplementation.hpp | 44 ++++++++++++------- OSBindings/Mac/Clock SignalTests/8088Tests.mm | 13 ++++-- 2 files changed, 37 insertions(+), 20 deletions(-) diff --git a/InstructionSets/x86/Implementation/PerformImplementation.hpp b/InstructionSets/x86/Implementation/PerformImplementation.hpp index 93a1ab80f..5b44d499b 100644 --- a/InstructionSets/x86/Implementation/PerformImplementation.hpp +++ b/InstructionSets/x86/Implementation/PerformImplementation.hpp @@ -502,6 +502,16 @@ void test(IntT &destination, IntT source, Status &status) { status.parity = result; } +template +void xchg(IntT &destination, IntT &source) { + /* + TEMP ← DEST + DEST ← SRC + SRC ← TEMP + */ + std::swap(destination, source); +} + template void mul(IntT &destination_high, IntT &destination_low, IntT source, Status &status) { /* @@ -1029,112 +1039,112 @@ template < instruction.displacement(), registers, flow_controller); - break; + return; case Operation::JNO: Primitive::jump( !status.condition(), instruction.displacement(), registers, flow_controller); - break; + return; case Operation::JB: Primitive::jump( status.condition(), instruction.displacement(), registers, flow_controller); - break; + return; case Operation::JNB: Primitive::jump( !status.condition(), instruction.displacement(), registers, flow_controller); - break; + return; case Operation::JZ: Primitive::jump( status.condition(), instruction.displacement(), registers, flow_controller); - break; + return; case Operation::JNZ: Primitive::jump( !status.condition(), instruction.displacement(), registers, flow_controller); - break; + return; case Operation::JBE: Primitive::jump( status.condition(), instruction.displacement(), registers, flow_controller); - break; + return; case Operation::JNBE: Primitive::jump( !status.condition(), instruction.displacement(), registers, flow_controller); - break; + return; case Operation::JS: Primitive::jump( status.condition(), instruction.displacement(), registers, flow_controller); - break; + return; case Operation::JNS: Primitive::jump( !status.condition(), instruction.displacement(), registers, flow_controller); - break; + return; case Operation::JP: Primitive::jump( !status.condition(), instruction.displacement(), registers, flow_controller); - break; + return; case Operation::JNP: Primitive::jump( status.condition(), instruction.displacement(), registers, flow_controller); - break; + return; case Operation::JL: Primitive::jump( status.condition(), instruction.displacement(), registers, flow_controller); - break; + return; case Operation::JNL: Primitive::jump( !status.condition(), instruction.displacement(), registers, flow_controller); - break; + return; case Operation::JLE: Primitive::jump( status.condition(), instruction.displacement(), registers, flow_controller); - break; + return; case Operation::JNLE: Primitive::jump( !status.condition(), instruction.displacement(), registers, flow_controller); - break; + return; case Operation::CLC: Primitive::clc(status); return; case Operation::CLD: Primitive::cld(status); return; @@ -1143,6 +1153,8 @@ template < case Operation::STD: Primitive::std(status); return; case Operation::STI: Primitive::sti(status); return; case Operation::CMC: Primitive::cmc(status); return; + + case Operation::XCHG: Primitive::xchg(destination(), source()); return; } // Write to memory if required to complete this operation. diff --git a/OSBindings/Mac/Clock SignalTests/8088Tests.mm b/OSBindings/Mac/Clock SignalTests/8088Tests.mm index 8fe9cb536..630c3f76e 100644 --- a/OSBindings/Mac/Clock SignalTests/8088Tests.mm +++ b/OSBindings/Mac/Clock SignalTests/8088Tests.mm @@ -418,11 +418,16 @@ struct FailedExecution { // @"80.7.json.gz", @"81.7.json.gz", @"83.7.json.gz", // TEST - @"84.json.gz", @"85.json.gz", - @"A8.json.gz", @"A9.json.gz", - @"F6.0.json.gz", @"F7.0.json.gz", +// @"84.json.gz", @"85.json.gz", +// @"A8.json.gz", @"A9.json.gz", +// @"F6.0.json.gz", @"F7.0.json.gz", - // TODO: XCHG, XLAT + // XCHG + @"86.json.gz", @"87.json.gz", + @"91.json.gz", @"92.json.gz", @"93.json.gz", @"94.json.gz", + @"95.json.gz", @"96.json.gz", @"97.json.gz", + + // TODO: XLAT // TODO: SALC, SETMO, SETMOC ]]; From 7753497a9395b5f960b77238c4cf9b1881c31c79 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 10 Oct 2023 22:35:25 -0400 Subject: [PATCH 065/128] Add header for std::swap. --- InstructionSets/x86/Implementation/PerformImplementation.hpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/InstructionSets/x86/Implementation/PerformImplementation.hpp b/InstructionSets/x86/Implementation/PerformImplementation.hpp index 5b44d499b..d80b9a749 100644 --- a/InstructionSets/x86/Implementation/PerformImplementation.hpp +++ b/InstructionSets/x86/Implementation/PerformImplementation.hpp @@ -13,6 +13,8 @@ #include "../../../Numeric/RegisterSizes.hpp" #include "../Interrupts.hpp" +#include + namespace InstructionSet::x86 { template From 4a803e2d4309445708675542ac56a8044d48272c Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 10 Oct 2023 22:43:06 -0400 Subject: [PATCH 066/128] Reduce ADD/ADC/SUB/SBB repetition. --- .../Implementation/PerformImplementation.hpp | 62 ++++--------------- OSBindings/Mac/Clock SignalTests/8088Tests.mm | 16 ++--- 2 files changed, 20 insertions(+), 58 deletions(-) diff --git a/InstructionSets/x86/Implementation/PerformImplementation.hpp b/InstructionSets/x86/Implementation/PerformImplementation.hpp index d80b9a749..db223d9f2 100644 --- a/InstructionSets/x86/Implementation/PerformImplementation.hpp +++ b/InstructionSets/x86/Implementation/PerformImplementation.hpp @@ -400,34 +400,15 @@ inline void das(uint8_t &al, Status &status) { status.zero = status.parity = al; } -template -void adc(IntT &destination, IntT source, Status &status) { - /* - DEST ← DEST + SRC + CF; - */ - /* - The OF, SF, ZF, AF, CF, and PF flags are set according to the result. - */ - const IntT result = destination + source + status.carry_bit(); - - status.carry = Numeric::carried_out() - 1>(destination, source, result); - status.auxiliary_carry = Numeric::carried_in<4>(destination, source, result); - status.sign = result & top_bit(); - status.zero = status.parity = result; - status.overflow = overflow(destination, source, result); - - destination = result; -} - -template +template void add(IntT &destination, IntT source, Status &status) { /* - DEST ← DEST + SRC; + DEST ← DEST + SRC [+ CF]; */ /* The OF, SF, ZF, AF, CF, and PF flags are set according to the result. */ - const IntT result = destination + source; + const IntT result = destination + source + (with_carry ? status.carry_bit() : 0); status.carry = Numeric::carried_out() - 1>(destination, source, result); status.auxiliary_carry = Numeric::carried_in<4>(destination, source, result); @@ -438,34 +419,15 @@ void add(IntT &destination, IntT source, Status &status) { destination = result; } -template -void sbb(IntT &destination, IntT source, Status &status) { - /* - DEST ← DEST - (SRC + CF); - */ - /* - The OF, SF, ZF, AF, CF, and PF flags are set according to the result. - */ - const IntT result = destination - source - status.carry_bit(); - - status.carry = Numeric::carried_out() - 1>(destination, source, result); - status.auxiliary_carry = Numeric::carried_in<4>(destination, source, result); - status.sign = result & top_bit(); - status.zero = status.parity = result; - status.overflow = overflow(destination, source, result); - - destination = result; -} - -template +template void sub(IntT &destination, IntT source, Status &status) { /* - DEST ← DEST - SRC; + DEST ← DEST - (SRC [+ CF]); */ /* The OF, SF, ZF, AF, CF, and PF flags are set according to the result. */ - const IntT result = destination - source; + const IntT result = destination - source - (with_borrow ? status.carry_bit() : 0); status.carry = Numeric::carried_out() - 1>(destination, source, result); status.auxiliary_carry = Numeric::carried_in<4>(destination, source, result); @@ -971,12 +933,12 @@ template < case Operation::HLT: flow_controller.halt(); return; case Operation::WAIT: flow_controller.wait(); return; - case Operation::ADC: Primitive::adc(destination(), source(), status); break; - case Operation::ADD: Primitive::add(destination(), source(), status); break; - case Operation::SBB: Primitive::sbb(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(), 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; // TODO: all the below could call a common registers getter? case Operation::MUL: diff --git a/OSBindings/Mac/Clock SignalTests/8088Tests.mm b/OSBindings/Mac/Clock SignalTests/8088Tests.mm index 630c3f76e..dc9b27823 100644 --- a/OSBindings/Mac/Clock SignalTests/8088Tests.mm +++ b/OSBindings/Mac/Clock SignalTests/8088Tests.mm @@ -297,7 +297,7 @@ struct FailedExecution { @"DC.json.gz", @"DD.json.gz", @"DE.json.gz", @"DE.json.gz", // Untested: HLT, WAIT - +*/ // ADC @"10.json.gz", @"11.json.gz", @"12.json.gz", @"13.json.gz", @"14.json.gz", @"15.json.gz", @"80.2.json.gz", @"81.2.json.gz", @"83.2.json.gz", @@ -313,7 +313,7 @@ struct FailedExecution { // SUB @"28.json.gz", @"29.json.gz", @"2A.json.gz", @"2B.json.gz", @"2C.json.gz", @"2D.json.gz", @"80.5.json.gz", @"81.5.json.gz", @"83.5.json.gz", - +/* // MUL @"F6.4.json.gz", @"F7.4.json.gz", @@ -413,9 +413,9 @@ struct FailedExecution { */ // CMP -// @"38.json.gz", @"39.json.gz", @"3A.json.gz", -// @"3B.json.gz", @"3C.json.gz", @"3D.json.gz", -// @"80.7.json.gz", @"81.7.json.gz", @"83.7.json.gz", + @"38.json.gz", @"39.json.gz", @"3A.json.gz", + @"3B.json.gz", @"3C.json.gz", @"3D.json.gz", + @"80.7.json.gz", @"81.7.json.gz", @"83.7.json.gz", // TEST // @"84.json.gz", @"85.json.gz", @@ -423,9 +423,9 @@ struct FailedExecution { // @"F6.0.json.gz", @"F7.0.json.gz", // XCHG - @"86.json.gz", @"87.json.gz", - @"91.json.gz", @"92.json.gz", @"93.json.gz", @"94.json.gz", - @"95.json.gz", @"96.json.gz", @"97.json.gz", +// @"86.json.gz", @"87.json.gz", +// @"91.json.gz", @"92.json.gz", @"93.json.gz", @"94.json.gz", +// @"95.json.gz", @"96.json.gz", @"97.json.gz", // TODO: XLAT // TODO: SALC, SETMO, SETMOC From fbd647080d0bc6d8cbe705ed33fabfee41dff70d Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 11 Oct 2023 11:06:20 -0400 Subject: [PATCH 067/128] Start factoring out useful ALU stuff. --- .../Implementation/PerformImplementation.hpp | 77 ++++------------- Numeric/Carry.hpp | 49 +++++++++++ OSBindings/Mac/Clock SignalTests/8088Tests.mm | 83 +++++++++---------- 3 files changed, 107 insertions(+), 102 deletions(-) diff --git a/InstructionSets/x86/Implementation/PerformImplementation.hpp b/InstructionSets/x86/Implementation/PerformImplementation.hpp index db223d9f2..59be6a9a3 100644 --- a/InstructionSets/x86/Implementation/PerformImplementation.hpp +++ b/InstructionSets/x86/Implementation/PerformImplementation.hpp @@ -149,47 +149,6 @@ IntT *resolve( namespace Primitive { -// -// BEGIN TEMPORARY COPY AND PASTE SECTION. -// -// The following are largely excised from the M68k PerformImplementation.hpp; if there proves to be no -// reason further to specialise them, there'll be a factoring out. In some cases I've tightened the documentation. -// - -/// @returns An int of type @c IntT with only the most-significant bit set. -template constexpr IntT top_bit() { - static_assert(!std::numeric_limits::is_signed); - constexpr IntT max = std::numeric_limits::max(); - return max - (max >> 1); -} - -/// @returns The number of bits in @c IntT. -template constexpr int bit_size() { - return sizeof(IntT) * 8; -} - -/// @returns An int with the top bit indicating whether overflow occurred during the calculation of -/// • @c lhs + @c rhs (if @c is_add is true); or -/// • @c lhs - @c rhs (if @c is_add is false) -/// and the result was @c result. All other bits will be clear. -template -IntT overflow(IntT lhs, IntT rhs, IntT result) { - const IntT output_changed = result ^ lhs; - const IntT input_differed = lhs ^ rhs; - - if constexpr (is_add) { - return top_bit() & output_changed & ~input_differed; - } else { - return top_bit() & output_changed & input_differed; - } -} -// NOTE TO FUTURE SELF: the original 68k `overflow` treats lhs and rhs the other way -// around, affecting subtractive overflow. Be careful. - -// -// END COPY AND PASTE SECTION. -// - // // Comments below on intended functioning of each operation come from the 1997 edition of the // Intel Architecture Software Developer’s Manual; that year all such definitions still fitted within a @@ -410,11 +369,11 @@ void add(IntT &destination, IntT source, Status &status) { */ const IntT result = destination + source + (with_carry ? status.carry_bit() : 0); - status.carry = Numeric::carried_out() - 1>(destination, source, result); + status.carry = Numeric::carried_out() - 1>(destination, source, result); status.auxiliary_carry = Numeric::carried_in<4>(destination, source, result); - status.sign = result & top_bit(); + status.sign = result & Numeric::top_bit(); status.zero = status.parity = result; - status.overflow = overflow(destination, source, result); + status.overflow = Numeric::overflow(destination, source, result); destination = result; } @@ -429,11 +388,11 @@ void sub(IntT &destination, IntT source, Status &status) { */ const IntT result = destination - source - (with_borrow ? status.carry_bit() : 0); - status.carry = Numeric::carried_out() - 1>(destination, source, result); + status.carry = Numeric::carried_out() - 1>(destination, source, result); status.auxiliary_carry = Numeric::carried_in<4>(destination, source, result); - status.sign = result & top_bit(); + status.sign = result & Numeric::top_bit(); status.zero = status.parity = result; - status.overflow = overflow(destination, source, result); + status.overflow = Numeric::overflow(destination, source, result); if constexpr (write_back) { destination = result; @@ -460,7 +419,7 @@ void test(IntT &destination, IntT source, Status &status) { */ const IntT result = destination & source; - status.sign = result & top_bit(); + status.sign = result & Numeric::top_bit(); status.zero = result; status.carry = status.overflow = 0; status.parity = result; @@ -529,7 +488,7 @@ void imul(IntT &destination_high, IntT &destination_low, IntT source, Status &st destination_high = (sIntT(destination_low) * sIntT(source)) >> (8 * sizeof(IntT)); destination_low = IntT(sIntT(destination_low) * sIntT(source)); - const auto sign_extension = (destination_low & top_bit()) ? IntT(~0) : 0; + const auto sign_extension = (destination_low & Numeric::top_bit()) ? IntT(~0) : 0; status.overflow = status.carry = destination_high != sign_extension; } @@ -657,8 +616,8 @@ void inc(IntT &destination, Status &status) { */ ++destination; - status.overflow = destination == top_bit(); - status.sign = destination & top_bit(); + status.overflow = destination == Numeric::top_bit(); + status.sign = destination & Numeric::top_bit(); status.zero = status.parity = destination; status.auxiliary_carry = ((destination - 1) ^ destination) & 0x10; } @@ -691,11 +650,11 @@ 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.overflow = destination == top_bit(); + status.overflow = destination == Numeric::top_bit(); --destination; - status.sign = destination & top_bit(); + status.sign = destination & Numeric::top_bit(); status.zero = status.parity = destination; status.auxiliary_carry = ((destination + 1) ^ destination) & 0x10; } @@ -713,7 +672,7 @@ void and_(IntT &destination, IntT source, Status &status) { status.overflow = 0; status.carry = 0; - status.sign = destination & top_bit(); + status.sign = destination & Numeric::top_bit(); status.zero = status.parity = destination; } @@ -730,7 +689,7 @@ void or_(IntT &destination, IntT source, Status &status) { status.overflow = 0; status.carry = 0; - status.sign = destination & top_bit(); + status.sign = destination & Numeric::top_bit(); status.zero = status.parity = destination; } @@ -747,7 +706,7 @@ void xor_(IntT &destination, IntT source, Status &status) { status.overflow = 0; status.carry = 0; - status.sign = destination & top_bit(); + status.sign = destination & Numeric::top_bit(); status.zero = status.parity = destination; } @@ -769,8 +728,8 @@ void neg(IntT &destination, Status &status) { destination = -destination; status.carry = destination; - status.overflow = destination == top_bit(); - status.sign = destination & top_bit(); + status.overflow = destination == Numeric::top_bit(); + status.sign = destination & Numeric::top_bit(); status.zero = status.parity = destination; } @@ -841,7 +800,7 @@ void cbw(IntT &ax) { template void cwd(IntT &dx, IntT ax) { - dx = ax & top_bit() ? IntT(~0) : IntT(0); + dx = ax & Numeric::top_bit() ? IntT(~0) : IntT(0); } inline void clc(Status &status) { status.carry = 0; } diff --git a/Numeric/Carry.hpp b/Numeric/Carry.hpp index 2dbf86e54..bc565df62 100644 --- a/Numeric/Carry.hpp +++ b/Numeric/Carry.hpp @@ -16,9 +16,17 @@ namespace Numeric { /// • borrow after calculating @c lhs - @c rhs if @c is_add is false; /// producing @c result. template bool carried_out(IntT lhs, IntT rhs, IntT result) { + // Additive: + // // 0 and 0 => didn't. // 0 and 1 or 1 and 0 => did if 0. // 1 and 1 => did. + // + // Subtractive: + // + // 1 and 0 => didn't + // 1 and 1 or 0 and 0 => did if 1. + // 0 and 1 => did. if constexpr (!is_add) { rhs = ~rhs; } @@ -40,6 +48,47 @@ template bool carried_in(IntT lhs, IntT rhs, IntT resul return IntT(1 << bit) & (lhs ^ rhs ^ result); } +// +// BEGIN TEMPORARY COPY AND PASTE SECTION. +// +// The following are largely excised from the M68k PerformImplementation.hpp; if there proves to be no +// reason further to specialise them, there'll be a factoring out. In some cases I've tightened the documentation. +// + +/// @returns An int of type @c IntT with only the most-significant bit set. +template constexpr IntT top_bit() { + static_assert(!std::numeric_limits::is_signed); + constexpr IntT max = std::numeric_limits::max(); + return max - (max >> 1); +} + +/// @returns The number of bits in @c IntT. +template constexpr int bit_size() { + return sizeof(IntT) * 8; +} + +/// @returns An int with the top bit indicating whether overflow occurred during the calculation of +/// • @c lhs + @c rhs (if @c is_add is true); or +/// • @c lhs - @c rhs (if @c is_add is false) +/// and the result was @c result. All other bits will be clear. +template +IntT overflow(IntT lhs, IntT rhs, IntT result) { + const IntT output_changed = result ^ lhs; + const IntT input_differed = lhs ^ rhs; + + if constexpr (is_add) { + return top_bit() & output_changed & ~input_differed; + } else { + return top_bit() & output_changed & input_differed; + } +} +// NOTE TO FUTURE SELF: the original 68k `overflow` treats lhs and rhs the other way +// around, affecting subtractive overflow. Be careful. + +// +// END COPY AND PASTE SECTION. +// + } #endif /* Carry_hpp */ diff --git a/OSBindings/Mac/Clock SignalTests/8088Tests.mm b/OSBindings/Mac/Clock SignalTests/8088Tests.mm index dc9b27823..9b3504346 100644 --- a/OSBindings/Mac/Clock SignalTests/8088Tests.mm +++ b/OSBindings/Mac/Clock SignalTests/8088Tests.mm @@ -282,7 +282,7 @@ struct FailedExecution { - (NSArray *)testFiles { NSString *path = [NSString stringWithUTF8String:TestSuiteHome]; NSSet *allowList = [NSSet setWithArray:@[ -/* @"37.json.gz", // AAA + @"37.json.gz", // AAA @"3F.json.gz", // AAS @"D4.json.gz", // AAM @"D5.json.gz", // AAD @@ -297,7 +297,7 @@ struct FailedExecution { @"DC.json.gz", @"DD.json.gz", @"DE.json.gz", @"DE.json.gz", // Untested: HLT, WAIT -*/ + // ADC @"10.json.gz", @"11.json.gz", @"12.json.gz", @"13.json.gz", @"14.json.gz", @"15.json.gz", @"80.2.json.gz", @"81.2.json.gz", @"83.2.json.gz", @@ -313,7 +313,7 @@ struct FailedExecution { // SUB @"28.json.gz", @"29.json.gz", @"2A.json.gz", @"2B.json.gz", @"2C.json.gz", @"2D.json.gz", @"80.5.json.gz", @"81.5.json.gz", @"83.5.json.gz", -/* + // MUL @"F6.4.json.gz", @"F7.4.json.gz", @@ -324,41 +324,41 @@ struct FailedExecution { @"F6.6.json.gz", @"F7.6.json.gz", // IDIV - @"F6.7.json.gz", @"F7.7.json.gz",*/ + @"F6.7.json.gz", @"F7.7.json.gz", // INC -// @"40.json.gz", @"41.json.gz", @"42.json.gz", @"43.json.gz", -// @"44.json.gz", @"45.json.gz", @"46.json.gz", @"47.json.gz", -// @"FE.0.json.gz", -// @"FF.0.json.gz", + @"40.json.gz", @"41.json.gz", @"42.json.gz", @"43.json.gz", + @"44.json.gz", @"45.json.gz", @"46.json.gz", @"47.json.gz", + @"FE.0.json.gz", + @"FF.0.json.gz", // DEC -// @"48.json.gz", @"49.json.gz", @"4A.json.gz", @"4B.json.gz", -// @"4C.json.gz", @"4D.json.gz", @"4E.json.gz", @"4F.json.gz", -// @"FE.1.json.gz", -// @"FF.1.json.gz", + @"48.json.gz", @"49.json.gz", @"4A.json.gz", @"4B.json.gz", + @"4C.json.gz", @"4D.json.gz", @"4E.json.gz", @"4F.json.gz", + @"FE.1.json.gz", + @"FF.1.json.gz", // TODO: IN, OUT -// @"70.json.gz", // JO -// @"71.json.gz", // JNO -// @"72.json.gz", // JB -// @"73.json.gz", // JNB -// @"74.json.gz", // JZ -// @"75.json.gz", // JNZ -// @"76.json.gz", // JBE -// @"77.json.gz", // JNBE -// @"78.json.gz", // JS -// @"79.json.gz", // JNS -// @"7A.json.gz", // JP -// @"7B.json.gz", // JNP -// @"7C.json.gz", // JL -// @"7D.json.gz", // JNL -// @"7E.json.gz", // JLE -// @"7F.json.gz", // JNLE + @"70.json.gz", // JO + @"71.json.gz", // JNO + @"72.json.gz", // JB + @"73.json.gz", // JNB + @"74.json.gz", // JZ + @"75.json.gz", // JNZ + @"76.json.gz", // JBE + @"77.json.gz", // JNBE + @"78.json.gz", // JS + @"79.json.gz", // JNS + @"7A.json.gz", // JP + @"7B.json.gz", // JNP + @"7C.json.gz", // JL + @"7D.json.gz", // JNL + @"7E.json.gz", // JLE + @"7F.json.gz", // JNLE // CALL -/* @"E8.json.gz", @"FF.2.json.gz", + @"E8.json.gz", @"FF.2.json.gz", @"9A.json.gz", @"FF.3.json.gz", // TODO: IRET @@ -388,15 +388,13 @@ struct FailedExecution { // XOR @"30.json.gz", @"31.json.gz", @"32.json.gz", @"33.json.gz", @"34.json.gz", @"35.json.gz", @"80.6.json.gz", @"81.6.json.gz", @"83.6.json.gz", -*/ -// // NEG -// @"F6.3.json.gz", @"F7.3.json.gz", -// -// // NOT -// @"F6.2.json.gz", @"F7.2.json.gz", + // NEG + @"F6.3.json.gz", @"F7.3.json.gz", + + // NOT + @"F6.2.json.gz", @"F7.2.json.gz", -/* // NOP @"90.json.gz", @@ -410,7 +408,6 @@ struct FailedExecution { @"FD.json.gz", // STD @"FB.json.gz", // STI @"F5.json.gz", // CMC -*/ // CMP @"38.json.gz", @"39.json.gz", @"3A.json.gz", @@ -418,14 +415,14 @@ struct FailedExecution { @"80.7.json.gz", @"81.7.json.gz", @"83.7.json.gz", // TEST -// @"84.json.gz", @"85.json.gz", -// @"A8.json.gz", @"A9.json.gz", -// @"F6.0.json.gz", @"F7.0.json.gz", + @"84.json.gz", @"85.json.gz", + @"A8.json.gz", @"A9.json.gz", + @"F6.0.json.gz", @"F7.0.json.gz", // XCHG -// @"86.json.gz", @"87.json.gz", -// @"91.json.gz", @"92.json.gz", @"93.json.gz", @"94.json.gz", -// @"95.json.gz", @"96.json.gz", @"97.json.gz", + @"86.json.gz", @"87.json.gz", + @"91.json.gz", @"92.json.gz", @"93.json.gz", @"94.json.gz", + @"95.json.gz", @"96.json.gz", @"97.json.gz", // TODO: XLAT // TODO: SALC, SETMO, SETMOC From 033ba75376f8cda99cd35cf7ef55b7555f5cdab7 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 11 Oct 2023 11:15:59 -0400 Subject: [PATCH 068/128] Standardise repetitive sign/zero/parity sets. --- .../Implementation/PerformImplementation.hpp | 74 ++++++++----------- InstructionSets/x86/Status.hpp | 57 ++++++++------ 2 files changed, 67 insertions(+), 64 deletions(-) diff --git a/InstructionSets/x86/Implementation/PerformImplementation.hpp b/InstructionSets/x86/Implementation/PerformImplementation.hpp index 59be6a9a3..8b06a4444 100644 --- a/InstructionSets/x86/Implementation/PerformImplementation.hpp +++ b/InstructionSets/x86/Implementation/PerformImplementation.hpp @@ -198,8 +198,7 @@ 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.sign = ax.halves.low & 0x80; - status.parity = status.zero = ax.halves.low; + status.set_from(ax.halves.low); } template @@ -223,8 +222,7 @@ inline void aam(CPU::RegisterPair16 &ax, uint8_t imm, Status &status, FlowContro ax.halves.high = ax.halves.low / imm; ax.halves.low = ax.halves.low % imm; - status.sign = ax.halves.low & 0x80; - status.parity = status.zero = ax.halves.low; + status.set_from(ax.halves.low); } inline void aas(CPU::RegisterPair16 &ax, Status &status) { @@ -303,8 +301,7 @@ inline void daa(uint8_t &al, Status &status) { status.carry = 0; } - status.sign = al & 0x80; - status.zero = status.parity = al; + status.set_from(al); } inline void das(uint8_t &al, Status &status) { @@ -355,8 +352,7 @@ inline void das(uint8_t &al, Status &status) { status.carry = 0; } - status.sign = al & 0x80; - status.zero = status.parity = al; + status.set_from(al); } template @@ -371,10 +367,10 @@ void add(IntT &destination, IntT source, Status &status) { status.carry = Numeric::carried_out() - 1>(destination, source, result); status.auxiliary_carry = Numeric::carried_in<4>(destination, source, result); - status.sign = result & Numeric::top_bit(); - status.zero = status.parity = result; status.overflow = Numeric::overflow(destination, source, result); + status.set_from(result); + destination = result; } @@ -390,10 +386,10 @@ void sub(IntT &destination, IntT source, Status &status) { status.carry = Numeric::carried_out() - 1>(destination, source, result); status.auxiliary_carry = Numeric::carried_in<4>(destination, source, result); - status.sign = result & Numeric::top_bit(); - status.zero = status.parity = result; status.overflow = Numeric::overflow(destination, source, result); + status.set_from(result); + if constexpr (write_back) { destination = result; } @@ -419,10 +415,8 @@ void test(IntT &destination, IntT source, Status &status) { */ const IntT result = destination & source; - status.sign = result & Numeric::top_bit(); - status.zero = result; status.carry = status.overflow = 0; - status.parity = result; + status.set_from(result); } template @@ -617,9 +611,8 @@ void inc(IntT &destination, Status &status) { ++destination; status.overflow = destination == Numeric::top_bit(); - status.sign = destination & Numeric::top_bit(); - status.zero = status.parity = destination; status.auxiliary_carry = ((destination - 1) ^ destination) & 0x10; + status.set_from(destination); } template @@ -654,8 +647,7 @@ void dec(IntT &destination, Status &status) { --destination; - status.sign = destination & Numeric::top_bit(); - status.zero = status.parity = destination; + status.set_from(destination); status.auxiliary_carry = ((destination + 1) ^ destination) & 0x10; } @@ -672,8 +664,7 @@ void and_(IntT &destination, IntT source, Status &status) { status.overflow = 0; status.carry = 0; - status.sign = destination & Numeric::top_bit(); - status.zero = status.parity = destination; + status.set_from(destination); } template @@ -689,8 +680,7 @@ void or_(IntT &destination, IntT source, Status &status) { status.overflow = 0; status.carry = 0; - status.sign = destination & Numeric::top_bit(); - status.zero = status.parity = destination; + status.set_from(destination); } template @@ -706,8 +696,7 @@ void xor_(IntT &destination, IntT source, Status &status) { status.overflow = 0; status.carry = 0; - status.sign = destination & Numeric::top_bit(); - status.zero = status.parity = destination; + status.set_from(destination); } template @@ -729,8 +718,7 @@ void neg(IntT &destination, Status &status) { status.carry = destination; status.overflow = destination == Numeric::top_bit(); - status.sign = destination & Numeric::top_bit(); - status.zero = status.parity = destination; + status.set_from(destination); } template @@ -958,112 +946,112 @@ template < case Operation::JO: Primitive::jump( - status.condition(), + status.condition(), instruction.displacement(), registers, flow_controller); return; case Operation::JNO: Primitive::jump( - !status.condition(), + !status.condition(), instruction.displacement(), registers, flow_controller); return; case Operation::JB: Primitive::jump( - status.condition(), + status.condition(), instruction.displacement(), registers, flow_controller); return; case Operation::JNB: Primitive::jump( - !status.condition(), + !status.condition(), instruction.displacement(), registers, flow_controller); return; case Operation::JZ: Primitive::jump( - status.condition(), + status.condition(), instruction.displacement(), registers, flow_controller); return; case Operation::JNZ: Primitive::jump( - !status.condition(), + !status.condition(), instruction.displacement(), registers, flow_controller); return; case Operation::JBE: Primitive::jump( - status.condition(), + status.condition(), instruction.displacement(), registers, flow_controller); return; case Operation::JNBE: Primitive::jump( - !status.condition(), + !status.condition(), instruction.displacement(), registers, flow_controller); return; case Operation::JS: Primitive::jump( - status.condition(), + status.condition(), instruction.displacement(), registers, flow_controller); return; case Operation::JNS: Primitive::jump( - !status.condition(), + !status.condition(), instruction.displacement(), registers, flow_controller); return; case Operation::JP: Primitive::jump( - !status.condition(), + !status.condition(), instruction.displacement(), registers, flow_controller); return; case Operation::JNP: Primitive::jump( - status.condition(), + status.condition(), instruction.displacement(), registers, flow_controller); return; case Operation::JL: Primitive::jump( - status.condition(), + status.condition(), instruction.displacement(), registers, flow_controller); return; case Operation::JNL: Primitive::jump( - !status.condition(), + !status.condition(), instruction.displacement(), registers, flow_controller); return; case Operation::JLE: Primitive::jump( - status.condition(), + status.condition(), instruction.displacement(), registers, flow_controller); return; case Operation::JNLE: Primitive::jump( - !status.condition(), + !status.condition(), instruction.displacement(), registers, flow_controller); diff --git a/InstructionSets/x86/Status.hpp b/InstructionSets/x86/Status.hpp index a678f1603..20907c181 100644 --- a/InstructionSets/x86/Status.hpp +++ b/InstructionSets/x86/Status.hpp @@ -9,6 +9,7 @@ #ifndef InstructionSets_x86_Status_hpp #define InstructionSets_x86_Status_hpp +#include "../../Numeric/Carry.hpp" namespace InstructionSet::x86 { @@ -53,6 +54,29 @@ static constexpr uint32_t VirtualMode = 1 << 17; } +enum class Flag { + Carry, + AuxiliaryCarry, + Sign, + Overflow, + Trap, + Interrupt, + Direction, + Zero, + ParityOdd +}; + +enum class Condition { + Overflow, + Below, + Zero, + BelowOrEqual, + Sign, + ParityOdd, + Less, + LessOrEqual +}; + struct Status { // Non-zero => set; zero => unset. uint32_t carry; @@ -70,17 +94,6 @@ struct Status { uint32_t parity; // Flag getters. - enum class Flag { - Carry, - AuxiliaryCarry, - Sign, - Overflow, - Trap, - Interrupt, - Direction, - Zero, - ParityOdd - }; template bool flag() { switch(flag) { case Flag::Carry: return carry; @@ -96,16 +109,6 @@ struct Status { } // Condition evaluation. - enum class Condition { - Overflow, - Below, - Zero, - BelowOrEqual, - Sign, - ParityOdd, - Less, - LessOrEqual - }; template bool condition() { switch(test) { case Condition::Overflow: return flag(); @@ -119,6 +122,18 @@ struct Status { } } + // Convenience setters. + template void set_from(IntT value) { + for(const auto flag: {flags...}) { + switch(flag) { + default: break; + case Flag::Zero: zero = value; break; + case Flag::Sign: sign = value & Numeric::top_bit(); break; + case Flag::ParityOdd: parity = value; break; + } + } + } + template IntT carry_bit() const { return carry ? 1 : 0; } bool not_parity_bit() const { // x86 parity always considers the lowest 8-bits only. From 71593663602d5d5e1acce35c6081ce073bea6dcd Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 11 Oct 2023 12:35:17 -0400 Subject: [PATCH 069/128] Collapse all flags accesses behind setters and getters. --- .../Implementation/PerformImplementation.hpp | 106 ++++----- InstructionSets/x86/Status.hpp | 202 ++++++++++-------- OSBindings/Mac/Clock SignalTests/8088Tests.mm | 4 +- 3 files changed, 170 insertions(+), 142 deletions(-) diff --git a/InstructionSets/x86/Implementation/PerformImplementation.hpp b/InstructionSets/x86/Implementation/PerformImplementation.hpp index 8b06a4444..7c8a668d8 100644 --- a/InstructionSets/x86/Implementation/PerformImplementation.hpp +++ b/InstructionSets/x86/Implementation/PerformImplementation.hpp @@ -175,12 +175,12 @@ 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.auxiliary_carry) { + if((ax.halves.low & 0x0f) > 9 || status.flag()) { ax.halves.low += 6; ++ax.halves.high; - status.auxiliary_carry = status.carry = 1; + status.set_from(1); } else { - status.auxiliary_carry = status.carry = 0; + status.set_from(0); } ax.halves.low &= 0x0f; } @@ -243,12 +243,12 @@ 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.auxiliary_carry) { + if((ax.halves.low & 0x0f) > 9 || status.flag()) { ax.halves.low -= 6; --ax.halves.high; - status.auxiliary_carry = status.carry = 1; + status.set_from(1); } else { - status.auxiliary_carry = status.carry = 0; + status.set_from(0); } ax.halves.low &= 0x0f; } @@ -283,22 +283,22 @@ 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.carry; - status.carry = 0; + const auto old_carry = status.flag(); + status.set_from(0); - if((al & 0x0f) > 0x09 || status.auxiliary_carry) { - status.carry = old_carry | (al > 0xf9); + if((al & 0x0f) > 0x09 || status.flag()) { + status.set_from(old_carry | (al > 0xf9)); al += 0x06; - status.auxiliary_carry = 1; + status.set_from(1); } else { - status.auxiliary_carry = 0; + status.set_from(0); } if(old_al > 0x99 || old_carry) { al += 0x60; - status.carry = 1; + status.set_from(1); } else { - status.carry = 0; + status.set_from(0); } status.set_from(al); @@ -334,22 +334,22 @@ 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.carry; - status.carry = 0; + const auto old_carry = status.flag(); + status.set_from(0); - if((al & 0x0f) > 0x09 || status.auxiliary_carry) { - status.carry = old_carry | (al < 0x06); + if((al & 0x0f) > 0x09 || status.flag()) { + status.set_from(old_carry | (al < 0x06)); al -= 0x06; - status.auxiliary_carry = 1; + status.set_from(1); } else { - status.auxiliary_carry = 0; + status.set_from(0); } if(old_al > 0x99 || old_carry) { al -= 0x60; - status.carry = 1; + status.set_from(1); } else { - status.carry = 0; + status.set_from(0); } status.set_from(al); @@ -365,9 +365,12 @@ void add(IntT &destination, IntT source, Status &status) { */ const IntT result = destination + source + (with_carry ? status.carry_bit() : 0); - status.carry = Numeric::carried_out() - 1>(destination, source, result); - status.auxiliary_carry = Numeric::carried_in<4>(destination, source, result); - status.overflow = Numeric::overflow(destination, source, result); + status.set_from( + Numeric::carried_out() - 1>(destination, source, result)); + status.set_from( + Numeric::carried_in<4>(destination, source, result)); + status.set_from( + Numeric::overflow(destination, source, result)); status.set_from(result); @@ -384,9 +387,12 @@ void sub(IntT &destination, IntT source, Status &status) { */ const IntT result = destination - source - (with_borrow ? status.carry_bit() : 0); - status.carry = Numeric::carried_out() - 1>(destination, source, result); - status.auxiliary_carry = Numeric::carried_in<4>(destination, source, result); - status.overflow = Numeric::overflow(destination, source, result); + status.set_from( + Numeric::carried_out() - 1>(destination, source, result)); + status.set_from( + Numeric::carried_in<4>(destination, source, result)); + status.set_from( + Numeric::overflow(destination, source, result)); status.set_from(result); @@ -415,7 +421,7 @@ void test(IntT &destination, IntT source, Status &status) { */ const IntT result = destination & source; - status.carry = status.overflow = 0; + status.set_from(0); status.set_from(result); } @@ -448,7 +454,7 @@ void mul(IntT &destination_high, IntT &destination_low, IntT source, Status &sta */ destination_high = (destination_low * source) >> (8 * sizeof(IntT)); destination_low *= source; - status.overflow = status.carry = destination_high; + status.set_from(destination_high); } template @@ -483,7 +489,7 @@ 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.overflow = status.carry = destination_high != sign_extension; + status.set_from(destination_high != sign_extension); } template @@ -610,8 +616,8 @@ void inc(IntT &destination, Status &status) { */ ++destination; - status.overflow = destination == Numeric::top_bit(); - status.auxiliary_carry = ((destination - 1) ^ destination) & 0x10; + status.set_from(destination == Numeric::top_bit()); + status.set_from(((destination - 1) ^ destination) & 0x10); status.set_from(destination); } @@ -643,12 +649,12 @@ 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.overflow = destination == Numeric::top_bit(); + status.set_from(destination == Numeric::top_bit()); --destination; status.set_from(destination); - status.auxiliary_carry = ((destination + 1) ^ destination) & 0x10; + status.set_from(((destination + 1) ^ destination) & 0x10); } template @@ -662,8 +668,7 @@ void and_(IntT &destination, IntT source, Status &status) { */ destination &= source; - status.overflow = 0; - status.carry = 0; + status.set_from(0); status.set_from(destination); } @@ -678,8 +683,7 @@ void or_(IntT &destination, IntT source, Status &status) { */ destination |= source; - status.overflow = 0; - status.carry = 0; + status.set_from(0); status.set_from(destination); } @@ -694,8 +698,7 @@ void xor_(IntT &destination, IntT source, Status &status) { */ destination ^= source; - status.overflow = 0; - status.carry = 0; + status.set_from(0); status.set_from(destination); } @@ -712,12 +715,12 @@ 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.auxiliary_carry = Numeric::carried_in<4>(IntT(0), destination, IntT(-destination)); + status.set_from(Numeric::carried_in<4>(IntT(0), destination, IntT(-destination))); destination = -destination; - status.carry = destination; - status.overflow = destination == Numeric::top_bit(); + status.set_from(destination); + status.set_from(destination == Numeric::top_bit()); status.set_from(destination); } @@ -791,13 +794,14 @@ void cwd(IntT &dx, IntT ax) { dx = ax & Numeric::top_bit() ? IntT(~0) : IntT(0); } -inline void clc(Status &status) { status.carry = 0; } -inline void cld(Status &status) { status.direction = 0; } -inline void cli(Status &status) { status.interrupt = 0; } // TODO: quite a bit more in protected mode. -inline void stc(Status &status) { status.carry = 1; } -inline void std(Status &status) { status.direction = 1; } -inline void sti(Status &status) { status.interrupt = 1; } // TODO: quite a bit more in protected mode. -inline void cmc(Status &status) { status.carry = !status.carry; } +// 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()); } } diff --git a/InstructionSets/x86/Status.hpp b/InstructionSets/x86/Status.hpp index 20907c181..f65042261 100644 --- a/InstructionSets/x86/Status.hpp +++ b/InstructionSets/x86/Status.hpp @@ -77,108 +77,132 @@ enum class Condition { LessOrEqual }; -struct Status { - // Non-zero => set; zero => unset. - uint32_t carry; - uint32_t auxiliary_carry; - uint32_t sign; - uint32_t overflow; - uint32_t trap; - uint32_t interrupt; - uint32_t direction; +class Status { + public: + using FlagT = uint32_t; - // Zero => set; non-zero => unset. - uint32_t zero; - - // Odd number of bits => set; even => unset. - uint32_t parity; - - // Flag getters. - template bool flag() { - switch(flag) { - case Flag::Carry: return carry; - case Flag::AuxiliaryCarry: return auxiliary_carry; - case Flag::Sign: return sign; - case Flag::Overflow: return overflow; - case Flag::Trap: return trap; - case Flag::Interrupt: return interrupt; - case Flag::Direction: return direction; - case Flag::Zero: return !zero; - case Flag::ParityOdd: return not_parity_bit(); - } - } - - // Condition evaluation. - template bool condition() { - switch(test) { - case Condition::Overflow: return flag(); - case Condition::Below: return flag(); - case Condition::Zero: return flag(); - case Condition::BelowOrEqual: return flag() || flag(); - case Condition::Sign: return flag(); - case Condition::ParityOdd: return flag(); - case Condition::Less: return flag() != flag(); - case Condition::LessOrEqual: return flag() || flag() != flag(); - } - } - - // Convenience setters. - template void set_from(IntT value) { - for(const auto flag: {flags...}) { + // Flag getters. + template bool flag() { switch(flag) { - default: break; - case Flag::Zero: zero = value; break; - case Flag::Sign: sign = value & Numeric::top_bit(); break; - case Flag::ParityOdd: parity = value; break; + case Flag::Carry: return carry; + case Flag::AuxiliaryCarry: return auxiliary_carry; + case Flag::Sign: return sign; + case Flag::Overflow: return overflow; + case Flag::Trap: return trap; + case Flag::Interrupt: return interrupt; + case Flag::Direction: return direction; + case Flag::Zero: return !zero; + case Flag::ParityOdd: return not_parity_bit(); } } - } - template IntT carry_bit() const { return carry ? 1 : 0; } - bool not_parity_bit() const { - // x86 parity always considers the lowest 8-bits only. - auto result = static_cast(parity); - result ^= result >> 4; - result ^= result >> 2; - result ^= result >> 1; - return result & 1; - } + // Condition evaluation. + template bool condition() { + switch(test) { + case Condition::Overflow: return flag(); + case Condition::Below: return flag(); + case Condition::Zero: return flag(); + case Condition::BelowOrEqual: return flag() || flag(); + case Condition::Sign: return flag(); + case Condition::ParityOdd: return flag(); + case Condition::Less: return flag() != flag(); + case Condition::LessOrEqual: return flag() || flag() != flag(); + } + } - // Complete value get and set. - void set(uint16_t value) { - carry = value & ConditionCode::Carry; - auxiliary_carry = value & ConditionCode::AuxiliaryCarry; - sign = value & ConditionCode::Sign; - overflow = value & ConditionCode::Overflow; - trap = value & ConditionCode::Trap; - interrupt = value & ConditionCode::Interrupt; - direction = value & ConditionCode::Direction; + // Convenience setters. - zero = (~value) & ConditionCode::Zero; + /// Sets all of @c flags as a function of @c value: + /// • Flag::Zero: sets the zero flag if @c value is zero; + /// • Flag::Sign: sets the sign flag if the top bit of @c value is one; + /// • Flag::ParityOdd: sets parity based on the low 8 bits of @c value; + /// • Flag::Carry: sets carry if @c value is non-zero; + /// • Flag::AuxiliaryCarry: sets auxiliary carry if @c value is non-zero; + /// • Flag::Overflow: sets overflow if @c value is non-zero; + /// • Flag::Interrupt: sets interrupt if @c value is non-zero; + /// • Flag::Trap: sets interrupt if @c value is non-zero; + /// • Flag::Direction: sets direction if @c value is non-zero. + template void set_from(IntT value) { + for(const auto flag: {flags...}) { + switch(flag) { + default: break; + case Flag::Zero: zero = value; break; + case Flag::Sign: sign = value & Numeric::top_bit(); break; + case Flag::ParityOdd: parity = value; break; + case Flag::Carry: carry = value; break; + case Flag::AuxiliaryCarry: auxiliary_carry = value; break; + case Flag::Overflow: overflow = value; break; + case Flag::Interrupt: interrupt = value; break; + case Flag::Trap: trap = value; break; + case Flag::Direction: direction = value; break; + } + } + } + template void set_from(FlagT value) { + set_from(value); + } - parity = (~value) & ConditionCode::Parity; - } + template IntT carry_bit() const { return carry ? 1 : 0; } + bool not_parity_bit() const { + // x86 parity always considers the lowest 8-bits only. + auto result = static_cast(parity); + result ^= result >> 4; + result ^= result >> 2; + result ^= result >> 1; + return result & 1; + } - uint16_t get() const { - return - 0xf002 | + // Complete value get and set. + void set(uint16_t value) { + carry = value & ConditionCode::Carry; + auxiliary_carry = value & ConditionCode::AuxiliaryCarry; + sign = value & ConditionCode::Sign; + overflow = value & ConditionCode::Overflow; + trap = value & ConditionCode::Trap; + interrupt = value & ConditionCode::Interrupt; + direction = value & ConditionCode::Direction; - (carry ? ConditionCode::Carry : 0) | - (auxiliary_carry ? ConditionCode::AuxiliaryCarry : 0) | - (sign ? ConditionCode::Sign : 0) | - (overflow ? ConditionCode::Overflow : 0) | - (trap ? ConditionCode::Trap : 0) | - (interrupt ? ConditionCode::Interrupt : 0) | - (direction ? ConditionCode::Direction : 0) | + zero = (~value) & ConditionCode::Zero; - (zero ? 0 : ConditionCode::Zero) | + parity = (~value) & ConditionCode::Parity; + } - (not_parity_bit() ? 0 : ConditionCode::Parity); - } + uint16_t get() const { + return + 0xf002 | - bool operator ==(const Status &rhs) const { - return get() == rhs.get(); - } + (carry ? ConditionCode::Carry : 0) | + (auxiliary_carry ? ConditionCode::AuxiliaryCarry : 0) | + (sign ? ConditionCode::Sign : 0) | + (overflow ? ConditionCode::Overflow : 0) | + (trap ? ConditionCode::Trap : 0) | + (interrupt ? ConditionCode::Interrupt : 0) | + (direction ? ConditionCode::Direction : 0) | + + (zero ? 0 : ConditionCode::Zero) | + + (not_parity_bit() ? 0 : ConditionCode::Parity); + } + + bool operator ==(const Status &rhs) const { + return get() == rhs.get(); + } + + private: + // Non-zero => set; zero => unset. + uint32_t carry; + uint32_t auxiliary_carry; + uint32_t sign; + uint32_t overflow; + uint32_t trap; + uint32_t interrupt; + uint32_t direction; + + // Zero => set; non-zero => unset. + uint32_t zero; + + // Odd number of bits => set; even => unset. + uint32_t parity; }; } diff --git a/OSBindings/Mac/Clock SignalTests/8088Tests.mm b/OSBindings/Mac/Clock SignalTests/8088Tests.mm index 9b3504346..8e7b690aa 100644 --- a/OSBindings/Mac/Clock SignalTests/8088Tests.mm +++ b/OSBindings/Mac/Clock SignalTests/8088Tests.mm @@ -197,8 +197,8 @@ class FlowController { push(status_.get(), true); - status_.interrupt = 0; - status_.trap = 0; + using Flag = InstructionSet::x86::Flag; + status_.set_from(0); // Push CS and IP. push(registers_.cs_); From a768b101f873e1750d9386689667961cd733c3bc Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 11 Oct 2023 14:36:42 -0400 Subject: [PATCH 070/128] Further clean up copy-and-paste mess. --- InstructionSets/x86/Decoder.cpp | 44 ++-- .../Implementation/PerformImplementation.hpp | 196 ++++-------------- InstructionSets/x86/Instruction.hpp | 4 + .../xcschemes/Clock Signal.xcscheme | 2 +- 4 files changed, 70 insertions(+), 176 deletions(-) diff --git a/InstructionSets/x86/Decoder.cpp b/InstructionSets/x86/Decoder.cpp index 0101ac84d..56410a244 100644 --- a/InstructionSets/x86/Decoder.cpp +++ b/InstructionSets/x86/Decoder.cpp @@ -80,7 +80,7 @@ std::pair::InstructionT> Decoder::decode(con #define Displacement(op, size) \ SetOperation(Operation::op); \ phase_ = Phase::DisplacementOrOperand; \ - displacement_size_ = size + operation_size_= displacement_size_ = size /// Handles PUSH [immediate], etc — anything with only an immediate operand. #define Immediate(op, size) \ @@ -90,11 +90,11 @@ std::pair::InstructionT> Decoder::decode(con operand_size_ = size /// Handles far CALL and far JMP — fixed four or six byte operand operations. -#define Far(op) \ - SetOperation(Operation::op); \ - phase_ = Phase::DisplacementOrOperand; \ - operand_size_ = DataSize::Word; \ - destination_ = Source::Immediate; \ +#define Far(op) \ + SetOperation(Operation::op); \ + phase_ = Phase::DisplacementOrOperand; \ + operation_size_ = operand_size_ = DataSize::Word; \ + destination_ = Source::Immediate; \ displacement_size_ = data_size(default_address_size_) /// Handles ENTER — a fixed three-byte operation. @@ -353,7 +353,7 @@ std::pair::InstructionT> Decoder::decode(con case 0x8e: MemRegReg(MOV, Seg_MemReg, DataSize::Word); break; case 0x8f: MemRegReg(POP, MemRegSingleOperand, data_size_); break; - case 0x90: Complete(NOP, None, None, DataSize::None); break; // Or XCHG AX, AX? + case 0x90: Complete(NOP, None, None, DataSize::Byte); break; // Could be encoded as XCHG AX, AX if Operation space becomes limited. case 0x91: Complete(XCHG, eAX, eCX, data_size_); break; case 0x92: Complete(XCHG, eAX, eDX, data_size_); break; case 0x93: Complete(XCHG, eAX, eBX, data_size_); break; @@ -365,7 +365,7 @@ std::pair::InstructionT> Decoder::decode(con case 0x98: Complete(CBW, eAX, AH, data_size_); break; case 0x99: Complete(CWD, eAX, eDX, data_size_); break; case 0x9a: Far(CALLfar); break; - case 0x9b: Complete(WAIT, None, None, DataSize::None); break; + case 0x9b: Complete(WAIT, None, None, DataSize::Byte); break; case 0x9c: Complete(PUSHF, None, None, data_size_); break; case 0x9d: Complete(POPF, None, None, data_size_); break; case 0x9e: Complete(SAHF, None, None, DataSize::Byte); break; @@ -421,11 +421,11 @@ std::pair::InstructionT> Decoder::decode(con source_ = Source::Immediate; operand_size_ = data_size_; } else { - Complete(RETnear, None, None, DataSize::None); + Complete(RETnear, None, None, DataSize::Byte); } break; case 0xc2: RegData(RETnear, None, data_size_); break; - case 0xc3: Complete(RETnear, None, None, DataSize::None); break; + case 0xc3: Complete(RETnear, None, None, DataSize::Byte); break; case 0xc4: MemRegReg(LES, Reg_MemReg, data_size_); break; case 0xc5: MemRegReg(LDS, Reg_MemReg, data_size_); break; case 0xc6: MemRegReg(MOV, MemRegMOV, DataSize::Byte); break; @@ -440,7 +440,7 @@ std::pair::InstructionT> Decoder::decode(con break; case 0xc9: if constexpr (model >= Model::i80186) { - Complete(LEAVE, None, None, DataSize::None); + Complete(LEAVE, None, None, DataSize::Byte); } else { Complete(RETfar, None, None, DataSize::DWord); } @@ -456,8 +456,8 @@ std::pair::InstructionT> Decoder::decode(con operand_ = 3; break; case 0xcd: RegData(INT, None, DataSize::Byte); break; - case 0xce: Complete(INTO, None, None, DataSize::None); break; - case 0xcf: Complete(IRET, None, None, DataSize::None); break; + case 0xce: Complete(INTO, None, None, DataSize::Byte); break; + case 0xcf: Complete(IRET, None, None, DataSize::Byte); break; case 0xd0: case 0xd1: ShiftGroup(); @@ -505,17 +505,17 @@ std::pair::InstructionT> Decoder::decode(con case 0xf2: repetition_ = Repetition::RepNE; break; case 0xf3: repetition_ = Repetition::RepE; break; - case 0xf4: Complete(HLT, None, None, DataSize::None); break; - case 0xf5: Complete(CMC, None, None, DataSize::None); break; + case 0xf4: Complete(HLT, None, None, DataSize::Byte); break; + case 0xf5: Complete(CMC, None, None, DataSize::Byte); break; case 0xf6: MemRegReg(Invalid, MemRegTEST_to_IDIV, DataSize::Byte); break; case 0xf7: MemRegReg(Invalid, MemRegTEST_to_IDIV, data_size_); break; - case 0xf8: Complete(CLC, None, None, DataSize::None); break; - case 0xf9: Complete(STC, None, None, DataSize::None); break; - case 0xfa: Complete(CLI, None, None, DataSize::None); break; - case 0xfb: Complete(STI, None, None, DataSize::None); break; - case 0xfc: Complete(CLD, None, None, DataSize::None); break; - case 0xfd: Complete(STD, None, None, DataSize::None); break; + case 0xf8: Complete(CLC, None, None, DataSize::Byte); break; + case 0xf9: Complete(STC, None, None, DataSize::Byte); break; + case 0xfa: Complete(CLI, None, None, DataSize::Byte); break; + case 0xfb: Complete(STI, None, None, DataSize::Byte); break; + case 0xfc: Complete(CLD, None, None, DataSize::Byte); break; + case 0xfd: Complete(STD, None, None, DataSize::Byte); break; case 0xfe: MemRegReg(Invalid, MemRegINC_DEC, DataSize::Byte); break; case 0xff: MemRegReg(Invalid, MemRegINC_to_PUSH, data_size_); break; @@ -541,7 +541,7 @@ std::pair::InstructionT> Decoder::decode(con case 0x03: MemRegReg(LSL, Reg_MemReg, data_size_); break; case 0x05: Requires(i80286); - Complete(LOADALL, None, None, DataSize::None); + Complete(LOADALL, None, None, DataSize::Byte); break; case 0x06: Complete(CLTS, None, None, DataSize::Byte); break; diff --git a/InstructionSets/x86/Implementation/PerformImplementation.hpp b/InstructionSets/x86/Implementation/PerformImplementation.hpp index 7c8a668d8..82f96153f 100644 --- a/InstructionSets/x86/Implementation/PerformImplementation.hpp +++ b/InstructionSets/x86/Implementation/PerformImplementation.hpp @@ -826,7 +826,7 @@ template < // Establish source() and destination() shorthand to fetch data if necessary. IntT immediate; - auto source = [&]() -> IntT& { + const auto source = [&]() -> IntT& { return *resolve( instruction, instruction.source().template source(), @@ -836,7 +836,7 @@ template < nullptr, &immediate); }; - auto destination = [&]() -> IntT& { + const auto destination = [&]() -> IntT& { return *resolve( instruction, instruction.destination().template source(), @@ -847,6 +847,25 @@ template < &immediate); }; + const auto jcc = [&](bool condition) { + Primitive::jump( + condition, + instruction.displacement(), + registers, + flow_controller); + }; + + const auto muldiv_high = [&]() -> IntT& { + if constexpr (data_size == DataSize::Byte) return registers.ah(); + else if constexpr (data_size == DataSize::Word) return registers.dx(); + else if constexpr (data_size == DataSize::DWord) return registers.edx(); + }; + const auto muldiv_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(); + }; + // Guide to the below: // // * use hard-coded register names where appropriate; @@ -891,43 +910,10 @@ template < case Operation::CMP: Primitive::sub(destination(), source(), status); break; case Operation::TEST: Primitive::test(destination(), source(), status); break; - // TODO: all the below could call a common registers getter? - case Operation::MUL: - if constexpr (data_size == DataSize::Byte) { - Primitive::mul(registers.ah(), registers.al(), source(), status); - } else if constexpr (data_size == DataSize::Word) { - Primitive::mul(registers.dx(), registers.ax(), source(), status); - } else if constexpr (data_size == DataSize::DWord) { - Primitive::mul(registers.edx(), registers.eax(), source(), status); - } - return; - case Operation::IMUL_1: - if constexpr (data_size == DataSize::Byte) { - Primitive::imul(registers.ah(), registers.al(), source(), status); - } else if constexpr (data_size == DataSize::Word) { - Primitive::imul(registers.dx(), registers.ax(), source(), status); - } else if constexpr (data_size == DataSize::DWord) { - Primitive::imul(registers.edx(), registers.eax(), source(), status); - } - return; - case Operation::DIV: - if constexpr (data_size == DataSize::Byte) { - Primitive::div(registers.ah(), registers.al(), source(), flow_controller); - } else if constexpr (data_size == DataSize::Word) { - Primitive::div(registers.dx(), registers.ax(), source(), flow_controller); - } else if constexpr (data_size == DataSize::DWord) { - Primitive::div(registers.edx(), registers.eax(), source(), flow_controller); - } - return; - case Operation::IDIV: - if constexpr (data_size == DataSize::Byte) { - Primitive::idiv(registers.ah(), registers.al(), source(), flow_controller); - } else if constexpr (data_size == DataSize::Word) { - Primitive::idiv(registers.dx(), registers.ax(), source(), flow_controller); - } else if constexpr (data_size == DataSize::DWord) { - Primitive::idiv(registers.edx(), registers.eax(), source(), flow_controller); - } - return; + case Operation::MUL: Primitive::mul(muldiv_high(), muldiv_low(), source(), status); return; + case Operation::IMUL_1: Primitive::imul(muldiv_high(), muldiv_low(), source(), status); return; + case Operation::DIV: Primitive::div(muldiv_high(), muldiv_low(), source(), flow_controller); return; + case Operation::IDIV: Primitive::idiv(muldiv_high(), muldiv_low(), source(), flow_controller); return; case Operation::INC: Primitive::inc(destination(), status); break; case Operation::DEC: Primitive::dec(destination(), status); break; @@ -948,118 +934,22 @@ template < Primitive::call_far(instruction, flow_controller, registers, memory); return; - case Operation::JO: - Primitive::jump( - status.condition(), - instruction.displacement(), - registers, - flow_controller); - return; - case Operation::JNO: - Primitive::jump( - !status.condition(), - instruction.displacement(), - registers, - flow_controller); - return; - case Operation::JB: - Primitive::jump( - status.condition(), - instruction.displacement(), - registers, - flow_controller); - return; - case Operation::JNB: - Primitive::jump( - !status.condition(), - instruction.displacement(), - registers, - flow_controller); - return; - case Operation::JZ: - Primitive::jump( - status.condition(), - instruction.displacement(), - registers, - flow_controller); - return; - case Operation::JNZ: - Primitive::jump( - !status.condition(), - instruction.displacement(), - registers, - flow_controller); - return; - case Operation::JBE: - Primitive::jump( - status.condition(), - instruction.displacement(), - registers, - flow_controller); - return; - case Operation::JNBE: - Primitive::jump( - !status.condition(), - instruction.displacement(), - registers, - flow_controller); - return; - case Operation::JS: - Primitive::jump( - status.condition(), - instruction.displacement(), - registers, - flow_controller); - return; - case Operation::JNS: - Primitive::jump( - !status.condition(), - instruction.displacement(), - registers, - flow_controller); - return; - case Operation::JP: - Primitive::jump( - !status.condition(), - instruction.displacement(), - registers, - flow_controller); - return; - case Operation::JNP: - Primitive::jump( - status.condition(), - instruction.displacement(), - registers, - flow_controller); - return; - case Operation::JL: - Primitive::jump( - status.condition(), - instruction.displacement(), - registers, - flow_controller); - return; - case Operation::JNL: - Primitive::jump( - !status.condition(), - instruction.displacement(), - registers, - flow_controller); - return; - case Operation::JLE: - Primitive::jump( - status.condition(), - instruction.displacement(), - registers, - flow_controller); - return; - case Operation::JNLE: - Primitive::jump( - !status.condition(), - instruction.displacement(), - registers, - flow_controller); - return; + case Operation::JO: jcc(status.condition()); return; + case Operation::JNO: jcc(!status.condition()); return; + case Operation::JB: jcc(status.condition()); return; + case Operation::JNB: jcc(!status.condition()); return; + case Operation::JZ: jcc(status.condition()); return; + case Operation::JNZ: jcc(!status.condition()); return; + case Operation::JBE: jcc(status.condition()); return; + case Operation::JNBE: jcc(!status.condition()); return; + case Operation::JS: jcc(status.condition()); return; + case Operation::JNS: jcc(!status.condition()); return; + case Operation::JP: jcc(!status.condition()); return; + case Operation::JNP: jcc(status.condition()); return; + case Operation::JL: jcc(status.condition()); return; + case Operation::JNL: jcc(!status.condition()); return; + case Operation::JLE: jcc(status.condition()); return; + case Operation::JNLE: jcc(!status.condition()); return; case Operation::CLC: Primitive::clc(status); return; case Operation::CLD: Primitive::cld(status); return; @@ -1105,9 +995,9 @@ template < if constexpr (is_32bit(model)) { perform(instruction, status, flow_controller, registers, memory, io); } - break; + [[fallthrough]]; case DataSize::None: - perform(instruction, status, flow_controller, registers, memory, io); + assert(false); break; } } diff --git a/InstructionSets/x86/Instruction.hpp b/InstructionSets/x86/Instruction.hpp index 3cd6f756f..41417e30f 100644 --- a/InstructionSets/x86/Instruction.hpp +++ b/InstructionSets/x86/Instruction.hpp @@ -789,6 +789,10 @@ template class Instruction { if(!has_length_extension()) return Repetition::None; return Repetition((length_extension() >> 4) & 3); } + + /// @returns The data size of this operation — e.g. `MOV AX, BX` has a data size of `::Word` but `MOV EAX, EBX` has a data size of + /// `::DWord`. This value is guaranteed never to be `DataSize::None` even for operations such as `CLI` that don't have operands and operate + /// on data that is not a byte, word or double word. DataSize operation_size() const { return DataSize(source_data_dest_sib_ >> 14); } diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock Signal.xcscheme b/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock Signal.xcscheme index 474fa352e..55c943904 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock Signal.xcscheme +++ b/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock Signal.xcscheme @@ -23,7 +23,7 @@ Date: Wed, 11 Oct 2023 15:08:04 -0400 Subject: [PATCH 071/128] Further generalise. --- .../Implementation/PerformImplementation.hpp | 32 +++++++------------ .../xcschemes/Clock Signal.xcscheme | 2 +- 2 files changed, 13 insertions(+), 21 deletions(-) diff --git a/InstructionSets/x86/Implementation/PerformImplementation.hpp b/InstructionSets/x86/Implementation/PerformImplementation.hpp index 82f96153f..685324897 100644 --- a/InstructionSets/x86/Implementation/PerformImplementation.hpp +++ b/InstructionSets/x86/Implementation/PerformImplementation.hpp @@ -847,6 +847,7 @@ template < &immediate); }; + // Performs a displacement jump only if @c condition is true. const auto jcc = [&](bool condition) { Primitive::jump( condition, @@ -855,12 +856,15 @@ template < flow_controller); }; - const auto muldiv_high = [&]() -> IntT& { + // Some instructions use a pair of registers as an extended accumulator — DX:AX or EDX:EAX. + // The two following return the high and low parts of that pair; they also work in Byte mode to return AH:AL, + // i.e. AX split into high and low parts. + const auto pair_high = [&]() -> IntT& { if constexpr (data_size == DataSize::Byte) return registers.ah(); else if constexpr (data_size == DataSize::Word) return registers.dx(); else if constexpr (data_size == DataSize::DWord) return registers.edx(); }; - const auto muldiv_low = [&]() -> IntT& { + 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(); @@ -882,20 +886,8 @@ template < case Operation::DAA: Primitive::daa(registers.al(), status); return; case Operation::DAS: Primitive::das(registers.al(), status); return; - case Operation::CBW: - if constexpr (data_size == DataSize::Word) { - Primitive::cbw(registers.ax()); - } else if constexpr (is_32bit(model) && data_size == DataSize::DWord) { - Primitive::cbw(registers.eax()); - } - return; - case Operation::CWD: - if constexpr (data_size == DataSize::Word) { - Primitive::cwd(registers.dx(), registers.ax()); - } else if constexpr (data_size == DataSize::DWord) { - Primitive::cwd(registers.edx(), registers.eax()); - } - return; + case Operation::CBW: Primitive::cbw(pair_low()); return; + case Operation::CWD: Primitive::cwd(pair_high(), pair_low()); return; case Operation::ESC: case Operation::NOP: return; @@ -910,10 +902,10 @@ template < case Operation::CMP: Primitive::sub(destination(), source(), status); break; case Operation::TEST: Primitive::test(destination(), source(), status); break; - case Operation::MUL: Primitive::mul(muldiv_high(), muldiv_low(), source(), status); return; - case Operation::IMUL_1: Primitive::imul(muldiv_high(), muldiv_low(), source(), status); return; - case Operation::DIV: Primitive::div(muldiv_high(), muldiv_low(), source(), flow_controller); return; - case Operation::IDIV: Primitive::idiv(muldiv_high(), muldiv_low(), source(), flow_controller); 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; diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock Signal.xcscheme b/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock Signal.xcscheme index 55c943904..474fa352e 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock Signal.xcscheme +++ b/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock Signal.xcscheme @@ -23,7 +23,7 @@ Date: Wed, 11 Oct 2023 16:01:09 -0400 Subject: [PATCH 072/128] Add INT (including INT3), INTO. --- .../Implementation/PerformImplementation.hpp | 23 +++++++++++++++---- OSBindings/Mac/Clock SignalTests/8088Tests.mm | 7 +++++- 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/InstructionSets/x86/Implementation/PerformImplementation.hpp b/InstructionSets/x86/Implementation/PerformImplementation.hpp index 685324897..f1defbfe5 100644 --- a/InstructionSets/x86/Implementation/PerformImplementation.hpp +++ b/InstructionSets/x86/Implementation/PerformImplementation.hpp @@ -202,7 +202,7 @@ inline void aad(CPU::RegisterPair16 &ax, uint8_t imm, Status &status) { } template -inline void aam(CPU::RegisterPair16 &ax, uint8_t imm, Status &status, FlowControllerT &flow_controller) { +void aam(CPU::RegisterPair16 &ax, uint8_t imm, Status &status, FlowControllerT &flow_controller) { /* tempAL ← AL; AH ← tempAL / imm8; (* imm8 is set to 0AH for the AAD mnemonic *) @@ -622,7 +622,7 @@ void inc(IntT &destination, Status &status) { } template -inline void jump(bool condition, IntT displacement, RegistersT ®isters, FlowControllerT &flow_controller) { +void jump(bool condition, IntT displacement, RegistersT ®isters, FlowControllerT &flow_controller) { /* IF condition THEN @@ -736,12 +736,12 @@ void not_(IntT &destination) { } template -inline void call_relative(IntT offset, RegistersT ®isters, FlowControllerT &flow_controller) { +void call_relative(IntT offset, RegistersT ®isters, FlowControllerT &flow_controller) { flow_controller.call(registers.ip() + offset); } template -inline void call_absolute(IntT target, FlowControllerT &flow_controller) { +void call_absolute(IntT target, FlowControllerT &flow_controller) { flow_controller.call(target); } @@ -777,6 +777,18 @@ void call_far(InstructionT &instruction, flow_controller.call(segment, offset); } +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 cbw(IntT &ax) { constexpr IntT test_bit = 1 << (sizeof(IntT) * 4 - 1); @@ -926,6 +938,9 @@ template < Primitive::call_far(instruction, flow_controller, registers, memory); return; + case Operation::INT: Primitive::int_(instruction.operand(), flow_controller); return; + case Operation::INTO: Primitive::into(status, flow_controller); return; + case Operation::JO: jcc(status.condition()); return; case Operation::JNO: jcc(!status.condition()); return; case Operation::JB: jcc(status.condition()); return; diff --git a/OSBindings/Mac/Clock SignalTests/8088Tests.mm b/OSBindings/Mac/Clock SignalTests/8088Tests.mm index 8e7b690aa..d9cfb059e 100644 --- a/OSBindings/Mac/Clock SignalTests/8088Tests.mm +++ b/OSBindings/Mac/Clock SignalTests/8088Tests.mm @@ -365,7 +365,12 @@ struct FailedExecution { // TODO: RET // TODO: JMP // TODO: JCXZ - // TODO: INT, INTO + + // INTO + @"CE.json.gz", + + // INT, INT3 + @"CC.json.gz", @"CD.json.gz", // TODO: LAHF, SAHF // TODO: LDS, LES From e948a678145dcca0ea9bca6c981efeb0f1049e17 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 12 Oct 2023 13:54:51 -0400 Subject: [PATCH 073/128] Implement SAHF, LAHF. --- .../Implementation/PerformImplementation.hpp | 28 +++++++++++++++++++ OSBindings/Mac/Clock SignalTests/8088Tests.mm | 10 ++++--- 2 files changed, 34 insertions(+), 4 deletions(-) diff --git a/InstructionSets/x86/Implementation/PerformImplementation.hpp b/InstructionSets/x86/Implementation/PerformImplementation.hpp index f1defbfe5..88e35246a 100644 --- a/InstructionSets/x86/Implementation/PerformImplementation.hpp +++ b/InstructionSets/x86/Implementation/PerformImplementation.hpp @@ -1,4 +1,5 @@ // +// // PerformImplementation.hpp // Clock Signal // @@ -789,6 +790,30 @@ void into(Status &status, FlowControllerT &flow_controller) { } } +inline void sahf(uint8_t &ah, Status &status) { + /* + EFLAGS(SF:ZF:0:AF:0:PF:1:CF) ← AH; + */ + status.set_from(ah); + status.set_from(!(ah & 0x40)); + status.set_from(ah & 0x10); + status.set_from(!(ah & 0x04)); + status.set_from(ah & 0x01); +} + +inline void lahf(uint8_t &ah, Status &status) { + /* + AH ← EFLAGS(SF:ZF:0:AF:0:PF:1:CF); + */ + ah = + (status.flag() ? 0x80 : 0x00) | + (status.flag() ? 0x40 : 0x00) | + (status.flag() ? 0x10 : 0x00) | + (status.flag() ? 0x00 : 0x04) | + 0x02 | + (status.flag() ? 0x01 : 0x00); +} + template void cbw(IntT &ax) { constexpr IntT test_bit = 1 << (sizeof(IntT) * 4 - 1); @@ -941,6 +966,9 @@ template < case Operation::INT: Primitive::int_(instruction.operand(), flow_controller); return; case Operation::INTO: Primitive::into(status, flow_controller); return; + case Operation::SAHF: Primitive::sahf(registers.ah(), status); return; + case Operation::LAHF: Primitive::lahf(registers.ah(), status); return; + case Operation::JO: jcc(status.condition()); return; case Operation::JNO: jcc(!status.condition()); return; case Operation::JB: jcc(status.condition()); return; diff --git a/OSBindings/Mac/Clock SignalTests/8088Tests.mm b/OSBindings/Mac/Clock SignalTests/8088Tests.mm index d9cfb059e..ed157cf01 100644 --- a/OSBindings/Mac/Clock SignalTests/8088Tests.mm +++ b/OSBindings/Mac/Clock SignalTests/8088Tests.mm @@ -282,7 +282,7 @@ struct FailedExecution { - (NSArray *)testFiles { NSString *path = [NSString stringWithUTF8String:TestSuiteHome]; NSSet *allowList = [NSSet setWithArray:@[ - @"37.json.gz", // AAA +/* @"37.json.gz", // AAA @"3F.json.gz", // AAS @"D4.json.gz", // AAM @"D5.json.gz", // AAD @@ -371,8 +371,10 @@ struct FailedExecution { // INT, INT3 @"CC.json.gz", @"CD.json.gz", +*/ + @"9E.json.gz", // SAHF + @"9F.json.gz", // LAHF - // TODO: LAHF, SAHF // TODO: LDS, LES // TODO: LEA @@ -381,7 +383,7 @@ struct FailedExecution { // TODO: LOOP, LOOPE, LOOPNE // TODO: MOV - +/* // AND @"20.json.gz", @"21.json.gz", @"22.json.gz", @"23.json.gz", @"24.json.gz", @"25.json.gz", @"80.4.json.gz", @"81.4.json.gz", @"83.4.json.gz", @@ -431,7 +433,7 @@ struct FailedExecution { // TODO: XLAT // TODO: SALC, SETMO, SETMOC - +*/ ]]; NSSet *ignoreList = nil; From cf846f501a310769961050584c795f1db569c0e1 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 12 Oct 2023 14:24:28 -0400 Subject: [PATCH 074/128] Implement LDS, LES. --- .../Implementation/PerformImplementation.hpp | 47 +++++++++++++++++-- OSBindings/Mac/Clock SignalTests/8088Tests.mm | 6 ++- 2 files changed, 46 insertions(+), 7 deletions(-) diff --git a/InstructionSets/x86/Implementation/PerformImplementation.hpp b/InstructionSets/x86/Implementation/PerformImplementation.hpp index 88e35246a..691a13b83 100644 --- a/InstructionSets/x86/Implementation/PerformImplementation.hpp +++ b/InstructionSets/x86/Implementation/PerformImplementation.hpp @@ -55,6 +55,21 @@ uint32_t address( return address + *resolve(instruction, pointer.base(), pointer, registers, memory); } +template +uint32_t address( + InstructionT &instruction, + DataPointer pointer, + RegistersT ®isters, + MemoryT &memory +) { + switch(pointer.source()) { + default: return 0; + case Source::Indirect: return address(instruction, pointer, registers, memory); + case Source::IndirectNoBase: return address(instruction, pointer, registers, memory); + case Source::DirectAddress: return address(instruction, pointer, registers, memory); + } +} + template IntT *resolve( InstructionT &instruction, @@ -750,11 +765,11 @@ template ()) { default: case Source::Immediate: flow_controller.call(instruction.segment(), instruction.offset()); return; @@ -778,6 +793,25 @@ void call_far(InstructionT &instruction, flow_controller.call(segment, offset); } +template +void ld( + InstructionT &instruction, + uint16_t &destination, + MemoryT &memory, + RegistersT ®isters +) { + const auto pointer = instruction.source(); + auto source_address = address(instruction, pointer, registers, memory); + const Source source_segment = pointer.segment(instruction.segment_override()); + + destination = memory.template access(source_segment, source_address); + source_address += 2; + switch(selector) { + case Source::DS: registers.ds() = memory.template access(source_segment, source_address); break; + case Source::ES: registers.es() = memory.template access(source_segment, source_address); break; + } +} + template void int_(uint8_t vector, FlowControllerT &flow_controller) { flow_controller.interrupt(vector); @@ -966,8 +1000,11 @@ template < case Operation::INT: Primitive::int_(instruction.operand(), flow_controller); return; case Operation::INTO: Primitive::into(status, flow_controller); return; - case Operation::SAHF: Primitive::sahf(registers.ah(), status); return; - case Operation::LAHF: Primitive::lahf(registers.ah(), status); return; + case Operation::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::JO: jcc(status.condition()); return; case Operation::JNO: jcc(!status.condition()); return; diff --git a/OSBindings/Mac/Clock SignalTests/8088Tests.mm b/OSBindings/Mac/Clock SignalTests/8088Tests.mm index ed157cf01..81a260344 100644 --- a/OSBindings/Mac/Clock SignalTests/8088Tests.mm +++ b/OSBindings/Mac/Clock SignalTests/8088Tests.mm @@ -371,11 +371,13 @@ struct FailedExecution { // INT, INT3 @"CC.json.gz", @"CD.json.gz", -*/ + @"9E.json.gz", // SAHF @"9F.json.gz", // LAHF +*/ + @"C5.json.gz", // LDS + @"C4.json.gz", // LES - // TODO: LDS, LES // TODO: LEA // TODO: CMPS, LODS, MOVS, SCAS, STOS From da029ee3446e7255e78863e3d7a762949ec20e61 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 12 Oct 2023 14:31:25 -0400 Subject: [PATCH 075/128] Implement LEA. --- .../x86/Implementation/PerformImplementation.hpp | 13 +++++++++++++ OSBindings/Mac/Clock SignalTests/8088Tests.mm | 6 +++--- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/InstructionSets/x86/Implementation/PerformImplementation.hpp b/InstructionSets/x86/Implementation/PerformImplementation.hpp index 691a13b83..484865917 100644 --- a/InstructionSets/x86/Implementation/PerformImplementation.hpp +++ b/InstructionSets/x86/Implementation/PerformImplementation.hpp @@ -812,6 +812,17 @@ void ld( } } +template +void lea( + InstructionT &instruction, + IntT &destination, + MemoryT &memory, + RegistersT ®isters +) { + // TODO: address size. + destination = IntT(address(instruction, instruction.source(), registers, memory)); +} + template void int_(uint8_t vector, FlowControllerT &flow_controller) { flow_controller.interrupt(vector); @@ -1006,6 +1017,8 @@ template < case Operation::LDS: if constexpr (data_size == DataSize::Word) Primitive::ld(instruction, destination(), memory, registers); return; case Operation::LES: if constexpr (data_size == DataSize::Word) Primitive::ld(instruction, destination(), memory, registers); return; + case Operation::LEA: Primitive::lea(instruction, destination(), memory, registers); return; + case Operation::JO: jcc(status.condition()); return; case Operation::JNO: jcc(!status.condition()); return; case Operation::JB: jcc(status.condition()); return; diff --git a/OSBindings/Mac/Clock SignalTests/8088Tests.mm b/OSBindings/Mac/Clock SignalTests/8088Tests.mm index 81a260344..def26027e 100644 --- a/OSBindings/Mac/Clock SignalTests/8088Tests.mm +++ b/OSBindings/Mac/Clock SignalTests/8088Tests.mm @@ -374,11 +374,11 @@ struct FailedExecution { @"9E.json.gz", // SAHF @"9F.json.gz", // LAHF -*/ + @"C5.json.gz", // LDS @"C4.json.gz", // LES - - // TODO: LEA +*/ + @"8D.json.gz", // LEA // TODO: CMPS, LODS, MOVS, SCAS, STOS From 97d3a9fa78b018a6ca24cc50cef8e168eedf2341 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 12 Oct 2023 15:34:46 -0400 Subject: [PATCH 076/128] Implement MOV. --- .../x86/Implementation/PerformImplementation.hpp | 6 ++++++ OSBindings/Mac/Clock SignalTests/8088Tests.mm | 11 ++++++++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/InstructionSets/x86/Implementation/PerformImplementation.hpp b/InstructionSets/x86/Implementation/PerformImplementation.hpp index 484865917..015091194 100644 --- a/InstructionSets/x86/Implementation/PerformImplementation.hpp +++ b/InstructionSets/x86/Implementation/PerformImplementation.hpp @@ -823,6 +823,11 @@ void lea( destination = IntT(address(instruction, instruction.source(), registers, memory)); } +template +void mov(IntT &destination, IntT source) { + destination = source; +} + template void int_(uint8_t vector, FlowControllerT &flow_controller) { flow_controller.interrupt(vector); @@ -1018,6 +1023,7 @@ template < case Operation::LES: if constexpr (data_size == DataSize::Word) Primitive::ld(instruction, destination(), memory, registers); return; case Operation::LEA: Primitive::lea(instruction, destination(), memory, registers); return; + case Operation::MOV: Primitive::mov(destination(), source()); return; case Operation::JO: jcc(status.condition()); return; case Operation::JNO: jcc(!status.condition()); return; diff --git a/OSBindings/Mac/Clock SignalTests/8088Tests.mm b/OSBindings/Mac/Clock SignalTests/8088Tests.mm index def26027e..9cc725bbb 100644 --- a/OSBindings/Mac/Clock SignalTests/8088Tests.mm +++ b/OSBindings/Mac/Clock SignalTests/8088Tests.mm @@ -384,7 +384,16 @@ struct FailedExecution { // TODO: LOOP, LOOPE, LOOPNE - // TODO: MOV + // MOV + @"88.json.gz", @"89.json.gz", @"8A.json.gz", @"8B.json.gz", + @"8C.json.gz", @"8E.json.gz", + @"A0.json.gz", @"A1.json.gz", @"A2.json.gz", @"A3.json.gz", + @"B0.json.gz", @"B1.json.gz", @"B2.json.gz", @"B3.json.gz", + @"B4.json.gz", @"B5.json.gz", @"B6.json.gz", @"B7.json.gz", + @"B8.json.gz", @"B9.json.gz", @"BA.json.gz", @"BB.json.gz", + @"BC.json.gz", @"BD.json.gz", @"BE.json.gz", @"BF.json.gz", + @"C6.json.gz", @"C7.json.gz", + /* // AND @"20.json.gz", @"21.json.gz", @"22.json.gz", @"23.json.gz", @"24.json.gz", @"25.json.gz", From d35377c7763b7c2a0b415599f518210865d69c94 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 12 Oct 2023 15:52:05 -0400 Subject: [PATCH 077/128] Implement SALC, SETMO, SETMOC. --- .../Implementation/PerformImplementation.hpp | 34 +++++++++++++++++++ InstructionSets/x86/Status.hpp | 4 +-- OSBindings/Mac/Clock SignalTests/8088Tests.mm | 12 +++++-- 3 files changed, 45 insertions(+), 5 deletions(-) diff --git a/InstructionSets/x86/Implementation/PerformImplementation.hpp b/InstructionSets/x86/Implementation/PerformImplementation.hpp index 015091194..19bd32de3 100644 --- a/InstructionSets/x86/Implementation/PerformImplementation.hpp +++ b/InstructionSets/x86/Implementation/PerformImplementation.hpp @@ -890,6 +890,24 @@ inline void std(Status &status) { status.set_from(1); } inline void sti(Status &status) { status.set_from(1); } inline void cmc(Status &status) { status.set_from(!status.flag()); } +inline void salc(uint8_t &al, const Status &status) { + al = status.flag() ? 0xff : 0x00; +} + +template +void setmo(IntT &destination, Status &status) { + destination = ~0; + status.set_from(0); + status.set_from(destination); +} + +template +void setmoc(IntT &destination, uint8_t cl, Status &status) { + if(cl) { + setmo(destination, status); + } +} + } template < @@ -1051,6 +1069,22 @@ template < case Operation::CMC: Primitive::cmc(status); return; case Operation::XCHG: Primitive::xchg(destination(), source()); return; + + case Operation::SALC: Primitive::salc(registers.al(), status); return; + case Operation::SETMO: + if constexpr (model == Model::i8086) { + Primitive::setmo(destination(), status); + } else { + // TODO. + } + return; + case Operation::SETMOC: + if constexpr (model == Model::i8086) { + Primitive::setmoc(destination(), registers.cl(), status); + } else { + // TODO. + } + return; } // Write to memory if required to complete this operation. diff --git a/InstructionSets/x86/Status.hpp b/InstructionSets/x86/Status.hpp index f65042261..71efb2f8c 100644 --- a/InstructionSets/x86/Status.hpp +++ b/InstructionSets/x86/Status.hpp @@ -82,7 +82,7 @@ class Status { using FlagT = uint32_t; // Flag getters. - template bool flag() { + template bool flag() const { switch(flag) { case Flag::Carry: return carry; case Flag::AuxiliaryCarry: return auxiliary_carry; @@ -97,7 +97,7 @@ class Status { } // Condition evaluation. - template bool condition() { + template bool condition() const { switch(test) { case Condition::Overflow: return flag(); case Condition::Below: return flag(); diff --git a/OSBindings/Mac/Clock SignalTests/8088Tests.mm b/OSBindings/Mac/Clock SignalTests/8088Tests.mm index 9cc725bbb..dd217b15a 100644 --- a/OSBindings/Mac/Clock SignalTests/8088Tests.mm +++ b/OSBindings/Mac/Clock SignalTests/8088Tests.mm @@ -377,13 +377,14 @@ struct FailedExecution { @"C5.json.gz", // LDS @"C4.json.gz", // LES -*/ @"8D.json.gz", // LEA +*/ // TODO: CMPS, LODS, MOVS, SCAS, STOS // TODO: LOOP, LOOPE, LOOPNE +/* // MOV @"88.json.gz", @"89.json.gz", @"8A.json.gz", @"8B.json.gz", @"8C.json.gz", @"8E.json.gz", @@ -394,7 +395,6 @@ struct FailedExecution { @"BC.json.gz", @"BD.json.gz", @"BE.json.gz", @"BF.json.gz", @"C6.json.gz", @"C7.json.gz", -/* // AND @"20.json.gz", @"21.json.gz", @"22.json.gz", @"23.json.gz", @"24.json.gz", @"25.json.gz", @"80.4.json.gz", @"81.4.json.gz", @"83.4.json.gz", @@ -443,8 +443,14 @@ struct FailedExecution { @"95.json.gz", @"96.json.gz", @"97.json.gz", // TODO: XLAT - // TODO: SALC, SETMO, SETMOC */ + @"D6.json.gz", // SALC + + // SETMO + @"D0.6.json.gz", @"D1.6.json.gz", + // SETMOC + @"D2.6.json.gz", @"D3.6.json.gz", + ]]; NSSet *ignoreList = nil; From bf832768e6c70a4ed550499676a6541c56a50a08 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 12 Oct 2023 21:12:03 -0400 Subject: [PATCH 078/128] Implement XLAT. --- .../Implementation/PerformImplementation.hpp | 25 ++++++++++++++++--- OSBindings/Mac/Clock SignalTests/8088Tests.mm | 14 +++++------ 2 files changed, 27 insertions(+), 12 deletions(-) diff --git a/InstructionSets/x86/Implementation/PerformImplementation.hpp b/InstructionSets/x86/Implementation/PerformImplementation.hpp index 19bd32de3..74df69705 100644 --- a/InstructionSets/x86/Implementation/PerformImplementation.hpp +++ b/InstructionSets/x86/Implementation/PerformImplementation.hpp @@ -814,7 +814,7 @@ void ld( template void lea( - InstructionT &instruction, + const InstructionT &instruction, IntT &destination, MemoryT &memory, RegistersT ®isters @@ -823,6 +823,23 @@ void lea( destination = IntT(address(instruction, instruction.source(), registers, memory)); } +template +void xlat( + const InstructionT &instruction, + MemoryT &memory, + RegistersT ®isters +) { + Source source_segment = instruction.segment_override(); + if(source_segment == Source::None) source_segment = Source::DS; + + AddressT address; + if constexpr (std::is_same_v) { + address = registers.bx() + registers.al(); + } + + registers.al() = memory.template access(source_segment, address); +} + template void mov(IntT &destination, IntT source) { destination = source; @@ -903,9 +920,7 @@ void setmo(IntT &destination, Status &status) { template void setmoc(IntT &destination, uint8_t cl, Status &status) { - if(cl) { - setmo(destination, status); - } + if(cl) setmo(destination, status); } } @@ -1085,6 +1100,8 @@ template < // TODO. } return; + + case Operation::XLAT: Primitive::xlat(instruction, memory, registers); return; } // Write to memory if required to complete this operation. diff --git a/OSBindings/Mac/Clock SignalTests/8088Tests.mm b/OSBindings/Mac/Clock SignalTests/8088Tests.mm index dd217b15a..bc61250b5 100644 --- a/OSBindings/Mac/Clock SignalTests/8088Tests.mm +++ b/OSBindings/Mac/Clock SignalTests/8088Tests.mm @@ -441,16 +441,14 @@ struct FailedExecution { @"86.json.gz", @"87.json.gz", @"91.json.gz", @"92.json.gz", @"93.json.gz", @"94.json.gz", @"95.json.gz", @"96.json.gz", @"97.json.gz", - - // TODO: XLAT */ - @"D6.json.gz", // SALC - - // SETMO - @"D0.6.json.gz", @"D1.6.json.gz", - // SETMOC - @"D2.6.json.gz", @"D3.6.json.gz", + @"D7.json.gz", // XLAT +/* + @"D6.json.gz", // SALC + @"D0.6.json.gz", @"D1.6.json.gz", // SETMO + @"D2.6.json.gz", @"D3.6.json.gz", // SETMOC +*/ ]]; NSSet *ignoreList = nil; From 1a0f848b21c5518e22bd18c7aa20c4abea8fd4b1 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 13 Oct 2023 14:44:22 -0400 Subject: [PATCH 079/128] Implement RCL. --- .../Implementation/PerformImplementation.hpp | 61 +++++++++++++++++++ OSBindings/Mac/Clock SignalTests/8088Tests.mm | 14 +++-- 2 files changed, 70 insertions(+), 5 deletions(-) diff --git a/InstructionSets/x86/Implementation/PerformImplementation.hpp b/InstructionSets/x86/Implementation/PerformImplementation.hpp index 74df69705..88d129436 100644 --- a/InstructionSets/x86/Implementation/PerformImplementation.hpp +++ b/InstructionSets/x86/Implementation/PerformImplementation.hpp @@ -923,6 +923,54 @@ void setmoc(IntT &destination, uint8_t cl, Status &status) { if(cl) setmo(destination, status); } +template +inline void rcl(IntT &destination, uint8_t count, Status &status) { + /* + (* RCL and RCR instructions *) + SIZE ← OperandSize + CASE (determine count) OF + SIZE = 8: tempCOUNT ← (COUNT AND 1FH) MOD 9; + SIZE = 16: tempCOUNT ← (COUNT AND 1FH) MOD 17; + SIZE = 32: tempCOUNT ← COUNT AND 1FH; + ESAC; + */ + if constexpr (model != Model::i8086) { + count &= 0x1f; + } + auto temp_count = count % (Numeric::bit_size() + 1); + + /* + (* RCL instruction operation *) + WHILE (tempCOUNT ≠ 0) + DO + tempCF ← MSB(DEST); + DEST ← (DEST * 2) + CF; + CF ← tempCF; + tempCOUNT ← tempCOUNT – 1; + OD; + ELIHW; + IF COUNT = 1 + THEN OF ← MSB(DEST) XOR CF; + ELSE OF is undefined; + FI; + */ + /* + The CF flag contains the value of the bit shifted into it. + The OF flag is affected only for single- bit rotates (see “Description” above); + it is undefined for multi-bit rotates. The SF, ZF, AF, and PF flags are not affected. + */ + auto carry = status.carry_bit(); + while(temp_count--) { + const IntT temp_carry = (destination >> (Numeric::bit_size() - 1)) & 1; + destination = (destination << 1) | carry; + carry = temp_carry; + } + status.set_from(carry); + status.set_from( + ((destination >> (Numeric::bit_size() - 1)) & 1) ^ carry + ); +} + } template < @@ -976,6 +1024,14 @@ template < flow_controller); }; + const auto shift_count = [&]() -> uint8_t { + switch(instruction.source().template source()) { + case Source::None: return 1; + case Source::Immediate: return uint8_t(instruction.operand()); + default: return registers.cl(); + } + }; + // Some instructions use a pair of registers as an extended accumulator — DX:AX or EDX:EAX. // The two following return the high and low parts of that pair; they also work in Byte mode to return AH:AL, // i.e. AX split into high and low parts. @@ -1075,6 +1131,8 @@ 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::CLC: Primitive::clc(status); return; case Operation::CLD: Primitive::cld(status); return; case Operation::CLI: Primitive::cli(status); return; @@ -1126,6 +1184,9 @@ template < // Dispatch to a function just like this that is specialised on data size. // Fetching will occur in that specialised function, per the overlapping // meaning of register names. + + // TODO: incorporate and propagate address size. + switch(instruction.operation_size()) { case DataSize::Byte: perform(instruction, status, flow_controller, registers, memory, io); diff --git a/OSBindings/Mac/Clock SignalTests/8088Tests.mm b/OSBindings/Mac/Clock SignalTests/8088Tests.mm index bc61250b5..58e8db2d1 100644 --- a/OSBindings/Mac/Clock SignalTests/8088Tests.mm +++ b/OSBindings/Mac/Clock SignalTests/8088Tests.mm @@ -415,10 +415,16 @@ struct FailedExecution { // NOP @"90.json.gz", +*/ // TODO: POP, POPF, PUSH, PUSHF - // TODO: RCL, RCR, ROL, ROR, SAL, SAR, SHR + // TODO: RCR, ROL, ROR, SAL, SAR, SHR + // RCL + @"D0.2.json.gz", @"D2.2.json.gz", + @"D1.2.json.gz", @"D3.2.json.gz", + +/* @"F8.json.gz", // CLC @"FC.json.gz", // CLD @"FA.json.gz", // CLI @@ -441,11 +447,9 @@ struct FailedExecution { @"86.json.gz", @"87.json.gz", @"91.json.gz", @"92.json.gz", @"93.json.gz", @"94.json.gz", @"95.json.gz", @"96.json.gz", @"97.json.gz", -*/ - @"D7.json.gz", // XLAT -/* - @"D6.json.gz", // SALC + @"D7.json.gz", // XLAT + @"D6.json.gz", // SALC @"D0.6.json.gz", @"D1.6.json.gz", // SETMO @"D2.6.json.gz", @"D3.6.json.gz", // SETMOC */ From 6ec291d96f16477d2358096ea8f8fb3cb7995d22 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 13 Oct 2023 15:34:06 -0400 Subject: [PATCH 080/128] Move ownership of mask test. --- .../x86/Implementation/PerformImplementation.hpp | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/InstructionSets/x86/Implementation/PerformImplementation.hpp b/InstructionSets/x86/Implementation/PerformImplementation.hpp index 88d129436..b6547de8d 100644 --- a/InstructionSets/x86/Implementation/PerformImplementation.hpp +++ b/InstructionSets/x86/Implementation/PerformImplementation.hpp @@ -934,11 +934,6 @@ inline void rcl(IntT &destination, uint8_t count, Status &status) { SIZE = 32: tempCOUNT ← COUNT AND 1FH; ESAC; */ - if constexpr (model != Model::i8086) { - count &= 0x1f; - } - auto temp_count = count % (Numeric::bit_size() + 1); - /* (* RCL instruction operation *) WHILE (tempCOUNT ≠ 0) @@ -959,6 +954,7 @@ inline void rcl(IntT &destination, uint8_t count, Status &status) { The OF flag is affected only for single- bit rotates (see “Description” above); it is undefined for multi-bit rotates. The SF, ZF, AF, and PF flags are not affected. */ + auto temp_count = count % (Numeric::bit_size() + 1); auto carry = status.carry_bit(); while(temp_count--) { const IntT temp_carry = (destination >> (Numeric::bit_size() - 1)) & 1; @@ -1025,10 +1021,11 @@ template < }; const auto shift_count = [&]() -> uint8_t { + static constexpr uint8_t mask = (model != Model::i8086) ? 0x1f : 0xff; switch(instruction.source().template source()) { case Source::None: return 1; - case Source::Immediate: return uint8_t(instruction.operand()); - default: return registers.cl(); + case Source::Immediate: return uint8_t(instruction.operand()) & mask; + default: return registers.cl() & mask; } }; From 6f7991f54adc5ecd4f4cd06e853ea87fcfc8efda Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 13 Oct 2023 21:32:35 -0400 Subject: [PATCH 081/128] Avoid loop. --- .../Implementation/PerformImplementation.hpp | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/InstructionSets/x86/Implementation/PerformImplementation.hpp b/InstructionSets/x86/Implementation/PerformImplementation.hpp index b6547de8d..dafe8b821 100644 --- a/InstructionSets/x86/Implementation/PerformImplementation.hpp +++ b/InstructionSets/x86/Implementation/PerformImplementation.hpp @@ -956,11 +956,23 @@ inline void rcl(IntT &destination, uint8_t count, Status &status) { */ auto temp_count = count % (Numeric::bit_size() + 1); auto carry = status.carry_bit(); - while(temp_count--) { - const IntT temp_carry = (destination >> (Numeric::bit_size() - 1)) & 1; - destination = (destination << 1) | carry; - carry = temp_carry; + switch(temp_count) { + case 0: break; + case Numeric::bit_size(): { + const IntT temp_carry = destination & 1; + destination = (destination >> 1) | (carry << (Numeric::bit_size() - 1)); + carry = temp_carry; + } break; + default: { + const IntT temp_carry = destination & (Numeric::top_bit() >> (temp_count - 1)); + destination = + (destination << temp_count) | + (destination >> (Numeric::bit_size() + 1 - temp_count)) | + (carry << (temp_count - 1)); + carry = temp_carry ? 1 : 0; + } break; } + status.set_from(carry); status.set_from( ((destination >> (Numeric::bit_size() - 1)) & 1) ^ carry From f45d8bcbdbced9e9f4f92edbd4c5113b6976c53d Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 13 Oct 2023 21:44:48 -0400 Subject: [PATCH 082/128] Implement RCR. --- .../Implementation/PerformImplementation.hpp | 50 +++++++++++++++++-- OSBindings/Mac/Clock SignalTests/8088Tests.mm | 10 ++-- 2 files changed, 54 insertions(+), 6 deletions(-) diff --git a/InstructionSets/x86/Implementation/PerformImplementation.hpp b/InstructionSets/x86/Implementation/PerformImplementation.hpp index dafe8b821..b66369c8e 100644 --- a/InstructionSets/x86/Implementation/PerformImplementation.hpp +++ b/InstructionSets/x86/Implementation/PerformImplementation.hpp @@ -923,7 +923,7 @@ void setmoc(IntT &destination, uint8_t cl, Status &status) { if(cl) setmo(destination, status); } -template +template inline void rcl(IntT &destination, uint8_t count, Status &status) { /* (* RCL and RCR instructions *) @@ -954,7 +954,7 @@ inline void rcl(IntT &destination, uint8_t count, Status &status) { The OF flag is affected only for single- bit rotates (see “Description” above); it is undefined for multi-bit rotates. The SF, ZF, AF, and PF flags are not affected. */ - auto temp_count = count % (Numeric::bit_size() + 1); + const auto temp_count = count % (Numeric::bit_size() + 1); auto carry = status.carry_bit(); switch(temp_count) { case 0: break; @@ -979,6 +979,49 @@ inline void rcl(IntT &destination, uint8_t count, Status &status) { ); } +template +inline void rcr(IntT &destination, uint8_t count, Status &status) { + /* + (* RCR instruction operation *) + IF COUNT = 1 + THEN OF ← MSB(DEST) XOR CF; + ELSE OF is undefined; + FI; + WHILE (tempCOUNT ≠ 0) + DO + tempCF ← LSB(SRC); + DEST ← (DEST / 2) + (CF * 2SIZE); + CF ← tempCF; + tempCOUNT ← tempCOUNT – 1; + OD; + */ + auto carry = status.carry_bit(); + status.set_from( + ((destination >> (Numeric::bit_size() - 1)) & 1) ^ carry + ); + + const auto temp_count = count % (Numeric::bit_size() + 1); + switch(temp_count) { + case 0: break; + case Numeric::bit_size(): { + const IntT temp_carry = destination & Numeric::top_bit(); + destination = (destination << 1) | carry; + carry = temp_carry; + } break; + default: { + const IntT temp_carry = destination & (1 << (temp_count - 1)); + destination = + (destination >> temp_count) | + (destination << (Numeric::bit_size() + 1 - temp_count)) | + (carry << (Numeric::bit_size() - temp_count)); + carry = temp_carry; + } break; + } + + status.set_from(carry); +} + + } template < @@ -1140,7 +1183,8 @@ 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::RCL: Primitive::rcl(destination(), shift_count(), status); break; + case Operation::RCR: Primitive::rcr(destination(), shift_count(), status); break; case Operation::CLC: Primitive::clc(status); return; case Operation::CLD: Primitive::cld(status); return; diff --git a/OSBindings/Mac/Clock SignalTests/8088Tests.mm b/OSBindings/Mac/Clock SignalTests/8088Tests.mm index 58e8db2d1..0d78ca6aa 100644 --- a/OSBindings/Mac/Clock SignalTests/8088Tests.mm +++ b/OSBindings/Mac/Clock SignalTests/8088Tests.mm @@ -418,11 +418,15 @@ struct FailedExecution { */ // TODO: POP, POPF, PUSH, PUSHF - // TODO: RCR, ROL, ROR, SAL, SAR, SHR + // TODO: ROL, ROR, SAL, SAR, SHR // RCL - @"D0.2.json.gz", @"D2.2.json.gz", - @"D1.2.json.gz", @"D3.2.json.gz", +// @"D0.2.json.gz", @"D2.2.json.gz", +// @"D1.2.json.gz", @"D3.2.json.gz", + + // RCR + @"D0.3.json.gz", @"D2.3.json.gz", + @"D1.3.json.gz", @"D3.3.json.gz", /* @"F8.json.gz", // CLC From f1cba4eb78e63f03746837ffe8ee5220c352d74c Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 13 Oct 2023 22:03:54 -0400 Subject: [PATCH 083/128] Implement remaining rolls. --- .../Implementation/PerformImplementation.hpp | 94 +++++++++++++++++++ OSBindings/Mac/Clock SignalTests/8088Tests.mm | 18 +++- 2 files changed, 109 insertions(+), 3 deletions(-) diff --git a/InstructionSets/x86/Implementation/PerformImplementation.hpp b/InstructionSets/x86/Implementation/PerformImplementation.hpp index b66369c8e..ae4ff7100 100644 --- a/InstructionSets/x86/Implementation/PerformImplementation.hpp +++ b/InstructionSets/x86/Implementation/PerformImplementation.hpp @@ -1021,7 +1021,99 @@ inline void rcr(IntT &destination, uint8_t count, Status &status) { status.set_from(carry); } +template +inline void rol(IntT &destination, uint8_t count, Status &status) { + /* + (* ROL and ROR instructions *) + SIZE ← OperandSize + CASE (determine count) OF + SIZE = 8: tempCOUNT ← COUNT MOD 8; + SIZE = 16: tempCOUNT ← COUNT MOD 16; + SIZE = 32: tempCOUNT ← COUNT MOD 32; + ESAC; + */ + /* + (* ROL instruction operation *) + WHILE (tempCOUNT ≠ 0) + DO + tempCF ← MSB(DEST); + DEST ← (DEST * 2) + tempCF; + tempCOUNT ← tempCOUNT – 1; + OD; + ELIHW; + IF COUNT = 1 + THEN OF ← MSB(DEST) XOR CF; + ELSE OF is undefined; + FI; + */ + /* + The CF flag contains the value of the bit shifted into it. + The OF flag is affected only for single- bit rotates (see “Description” above); + it is undefined for multi-bit rotates. The SF, ZF, AF, and PF flags are not affected. + */ + const auto temp_count = count & (Numeric::bit_size() - 1); + if(!count) { + // TODO: is this 8086-specific? i.e. do the other x86s also exit without affecting flags when temp_count = 0? + return; + } + if(temp_count) { + destination = + (destination << temp_count) | + (destination >> (Numeric::bit_size() - temp_count)); + } + status.set_from(destination & 1); + status.set_from( + ((destination >> (Numeric::bit_size() - 1)) ^ destination) & 1 + ); +} + +template +inline void ror(IntT &destination, uint8_t count, Status &status) { + /* + (* ROL and ROR instructions *) + SIZE ← OperandSize + CASE (determine count) OF + SIZE = 8: tempCOUNT ← COUNT MOD 8; + SIZE = 16: tempCOUNT ← COUNT MOD 16; + SIZE = 32: tempCOUNT ← COUNT MOD 32; + ESAC; + */ + /* + (* ROR instruction operation *) + WHILE (tempCOUNT ≠ 0) + DO + tempCF ← LSB(DEST); + DEST ← (DEST / 2) + (tempCF * 2^SIZE); + tempCOUNT ← tempCOUNT – 1; + OD; + ELIHW; + IF COUNT = 1 + THEN OF ← MSB(DEST) XOR MSB - 1 (DEST); + ELSE OF is undefined; + FI; + */ + /* + The CF flag contains the value of the bit shifted into it. + The OF flag is affected only for single- bit rotates (see “Description” above); + it is undefined for multi-bit rotates. The SF, ZF, AF, and PF flags are not affected. + */ + const auto temp_count = count & (Numeric::bit_size() - 1); + if(!count) { + // TODO: is this 8086-specific? i.e. do the other x86s also exit without affecting flags when temp_count = 0? + return; + } + if(temp_count) { + destination = + (destination >> temp_count) | + (destination << (Numeric::bit_size() - temp_count)); + } + + status.set_from(destination & Numeric::top_bit()); + status.set_from( + (destination ^ (destination << 1)) & Numeric::top_bit() + ); +} } template < @@ -1185,6 +1277,8 @@ template < 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::CLC: Primitive::clc(status); return; case Operation::CLD: Primitive::cld(status); return; diff --git a/OSBindings/Mac/Clock SignalTests/8088Tests.mm b/OSBindings/Mac/Clock SignalTests/8088Tests.mm index 0d78ca6aa..c8726a549 100644 --- a/OSBindings/Mac/Clock SignalTests/8088Tests.mm +++ b/OSBindings/Mac/Clock SignalTests/8088Tests.mm @@ -418,16 +418,24 @@ struct FailedExecution { */ // TODO: POP, POPF, PUSH, PUSHF - // TODO: ROL, ROR, SAL, SAR, SHR + // TODO: SAL, SAR, SHR // RCL -// @"D0.2.json.gz", @"D2.2.json.gz", -// @"D1.2.json.gz", @"D3.2.json.gz", + @"D0.2.json.gz", @"D2.2.json.gz", + @"D1.2.json.gz", @"D3.2.json.gz", // RCR @"D0.3.json.gz", @"D2.3.json.gz", @"D1.3.json.gz", @"D3.3.json.gz", + // ROL + @"D0.0.json.gz", @"D2.0.json.gz", + @"D1.0.json.gz", @"D3.0.json.gz", + + // ROR + @"D0.1.json.gz", @"D2.1.json.gz", + @"D1.1.json.gz", @"D3.1.json.gz", + /* @"F8.json.gz", // CLC @"FC.json.gz", // CLD @@ -636,6 +644,10 @@ struct FailedExecution { execution_support.status = initial_status; execution_support.registers = initial_registers; + if([test[@"name"] isEqual:@"rol byte ss:[bp+si+CF11h], cl"]) { + printf(""); + } + // Execute instruction. execution_support.registers.ip_ += decoded.first; InstructionSet::x86::perform( From e38fe7dffcfcc91b1cf0fc5996a0a2593bebaf1e Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 14 Oct 2023 21:42:33 -0400 Subject: [PATCH 084/128] Implement SAL, SAR. --- .../Implementation/PerformImplementation.hpp | 105 ++++++++++++++++++ OSBindings/Mac/Clock SignalTests/8088Tests.mm | 10 +- 2 files changed, 114 insertions(+), 1 deletion(-) diff --git a/InstructionSets/x86/Implementation/PerformImplementation.hpp b/InstructionSets/x86/Implementation/PerformImplementation.hpp index ae4ff7100..fe27bbbde 100644 --- a/InstructionSets/x86/Implementation/PerformImplementation.hpp +++ b/InstructionSets/x86/Implementation/PerformImplementation.hpp @@ -1114,6 +1114,109 @@ inline void ror(IntT &destination, uint8_t count, Status &status) { (destination ^ (destination << 1)) & Numeric::top_bit() ); } + +/* + tempCOUNT ← (COUNT AND 1FH); + tempDEST ← DEST; + WHILE (tempCOUNT ≠ 0) + DO + IF instruction is SAL or SHL + THEN + CF ← MSB(DEST); + ELSE (* instruction is SAR or SHR *) + CF ← LSB(DEST); + FI; + IF instruction is SAL or SHL + THEN + DEST ← DEST ∗ 2; + ELSE + IF instruction is SAR + THEN + DEST ← DEST / 2 (*Signed divide, rounding toward negative infinity*); + ELSE (* instruction is SHR *) + DEST ← DEST / 2 ; (* Unsigned divide *); + FI; + FI; + tempCOUNT ← tempCOUNT – 1; + OD; + (* Determine overflow for the various instructions *) + IF COUNT = 1 + THEN + IF instruction is SAL or SHL + THEN + OF ← MSB(DEST) XOR CF; + ELSE + IF instruction is SAR + THEN + OF ← 0; + ELSE (* instruction is SHR *) + OF ← MSB(tempDEST); + FI; + FI; + ELSE + IF COUNT = 0 + THEN + All flags remain unchanged; + ELSE (* COUNT neither 1 or 0 *) + OF ← undefined; + FI; + FI; +*/ +/* + The CF flag contains the value of the last bit shifted out of the destination operand; + it is undefined for SHL and SHR instructions where the count is greater than or equal to + the size (in bits) of the destination operand. The OF flag is affected only for 1-bit shifts + (see “Description” above); otherwise, it is undefined. + + The SF, ZF, and PF flags are set according to the result. If the count is 0, the flags are not affected. + For a non-zero count, the AF flag is undefined. +*/ +template +inline void sal(IntT &destination, uint8_t count, Status &status) { + switch(count) { + case 0: return; + case Numeric::bit_size(): + status.set_from(destination & 1); + destination = 0; + break; + default: + if(count > Numeric::bit_size()) { + status.set_from(0); + destination = 0; + } else { + const auto mask = (Numeric::top_bit() >> (count - 1)); + status.set_from( + destination & mask + ); + status.set_from( + (destination ^ (destination << 1)) & mask + ); + destination <<= count; + } + break; + } + status.set_from(destination); +} + +template +inline void sar(IntT &destination, uint8_t count, Status &status) { + if(!count) { + return; + } + + const IntT sign = Numeric::top_bit() & destination; + if(count >= Numeric::bit_size()) { + destination = sign ? IntT(~0) : IntT(0); + status.set_from(sign); + } else { + const IntT mask = 1 << (count - 1); + status.set_from(destination & mask); + destination = (destination >> count) | (sign ? ~(IntT(~0) >> count) : 0); + } + status.set_from(0); + status.set_from(destination); +} + } template < @@ -1279,6 +1382,8 @@ template < 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::CLC: Primitive::clc(status); return; case Operation::CLD: Primitive::cld(status); return; diff --git a/OSBindings/Mac/Clock SignalTests/8088Tests.mm b/OSBindings/Mac/Clock SignalTests/8088Tests.mm index c8726a549..9ddb18646 100644 --- a/OSBindings/Mac/Clock SignalTests/8088Tests.mm +++ b/OSBindings/Mac/Clock SignalTests/8088Tests.mm @@ -415,7 +415,6 @@ struct FailedExecution { // NOP @"90.json.gz", -*/ // TODO: POP, POPF, PUSH, PUSHF // TODO: SAL, SAR, SHR @@ -435,6 +434,15 @@ struct FailedExecution { // ROR @"D0.1.json.gz", @"D2.1.json.gz", @"D1.1.json.gz", @"D3.1.json.gz", +*/ + + // SAL +// @"D0.4.json.gz", @"D2.4.json.gz", +// @"D1.4.json.gz", @"D3.4.json.gz", + + // SAR + @"D0.7.json.gz", @"D2.7.json.gz", + @"D1.7.json.gz", @"D3.7.json.gz", /* @"F8.json.gz", // CLC From f1779e60678a0a31f39ab109c74b03d81b018412 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 16 Oct 2023 12:34:11 -0400 Subject: [PATCH 085/128] Implement SHR. --- .../Implementation/PerformImplementation.hpp | 22 ++++++++ OSBindings/Mac/Clock SignalTests/8088Tests.mm | 56 ++++++++----------- 2 files changed, 46 insertions(+), 32 deletions(-) diff --git a/InstructionSets/x86/Implementation/PerformImplementation.hpp b/InstructionSets/x86/Implementation/PerformImplementation.hpp index fe27bbbde..75f470142 100644 --- a/InstructionSets/x86/Implementation/PerformImplementation.hpp +++ b/InstructionSets/x86/Implementation/PerformImplementation.hpp @@ -1217,6 +1217,27 @@ inline void sar(IntT &destination, uint8_t count, Status &status) { status.set_from(destination); } +template +inline void shr(IntT &destination, uint8_t count, Status &status) { + if(!count) { + return; + } + + status.set_from(Numeric::top_bit() & destination); + if(count == Numeric::bit_size()) { + status.set_from(Numeric::top_bit() & destination); + destination = 0; + } else if(count > Numeric::bit_size()) { + status.set_from(0); + destination = 0; + } else { + const IntT mask = 1 << (count - 1); + status.set_from(destination & mask); + destination >>= count; + } + status.set_from(destination); +} + } template < @@ -1384,6 +1405,7 @@ template < case Operation::ROR: Primitive::ror(destination(), shift_count(), status); break; case Operation::SAL: Primitive::sal(destination(), shift_count(), status); break; case Operation::SAR: Primitive::sar(destination(), shift_count(), status); break; + case Operation::SHR: Primitive::shr(destination(), shift_count(), status); break; case Operation::CLC: Primitive::clc(status); return; case Operation::CLD: Primitive::cld(status); return; diff --git a/OSBindings/Mac/Clock SignalTests/8088Tests.mm b/OSBindings/Mac/Clock SignalTests/8088Tests.mm index 9ddb18646..ecbf7226d 100644 --- a/OSBindings/Mac/Clock SignalTests/8088Tests.mm +++ b/OSBindings/Mac/Clock SignalTests/8088Tests.mm @@ -265,7 +265,8 @@ struct ExecutionSupport { }; struct FailedExecution { - std::string file, test_name; + std::string test_name; + std::string reason; InstructionSet::x86::Instruction instruction; }; @@ -417,7 +418,6 @@ struct FailedExecution { @"90.json.gz", // TODO: POP, POPF, PUSH, PUSHF - // TODO: SAL, SAR, SHR // RCL @"D0.2.json.gz", @"D2.2.json.gz", @@ -441,8 +441,12 @@ struct FailedExecution { // @"D1.4.json.gz", @"D3.4.json.gz", // SAR - @"D0.7.json.gz", @"D2.7.json.gz", - @"D1.7.json.gz", @"D3.7.json.gz", +// @"D0.7.json.gz", @"D2.7.json.gz", +// @"D1.7.json.gz", @"D3.7.json.gz", + + // SHR + @"D0.5.json.gz", @"D2.5.json.gz", + @"D1.5.json.gz", @"D3.5.json.gz", /* @"F8.json.gz", // CLC @@ -694,36 +698,24 @@ struct FailedExecution { if(!statusEqual || !registersEqual || !ramEqual) { FailedExecution failure; failure.instruction = decoded.second; -// failure.file = // TODO failure.test_name = std::string([test[@"name"] UTF8String]); + + NSMutableArray *reasons = [[NSMutableArray alloc] init]; + if(!statusEqual) { + [reasons addObject: + [NSString stringWithFormat:@"status differs by %04x", + (intended_status.get() ^ execution_support.status.get()) & flags_mask]]; + } + if(!registersEqual) { + [reasons addObject:@"registers don't match"]; + } + if(!ramEqual) { + [reasons addObject:@"RAM contents don't match"]; + } + + failure.reason = std::string([reasons componentsJoinedByString:@"; "].UTF8String); execution_failures.push_back(std::move(failure)); } - -/* if(assert) { - XCTAssert( - statusEqual, - "Status doesn't match despite mask %04x — differs in %04x after %@; executing %@", - flags_mask, - (intended_status.get() ^ execution_support.status.get()) & flags_mask, - test[@"name"], - [self toString:decoded.second offsetLength:4 immediateLength:4] - ); - // TODO: should probably say more about the following two. - XCTAssert( - registersEqual, - "Register mismatch after %@; executing %@", - test[@"name"], - [self toString:decoded.second offsetLength:4 immediateLength:4] - ); - XCTAssert( - ramEqual, - "Memory contents mismatch after %@; executing %@", - test[@"name"], - [self toString:decoded.second offsetLength:4 immediateLength:4] - ); - } - - return statusEqual && registersEqual && ramEqual;*/ } - (void)printFailures:(NSArray *)failures { @@ -775,7 +767,7 @@ struct FailedExecution { XCTAssertEqual(execution_failures.size(), 0); for(const auto &failure: execution_failures) { - NSLog(@"Failed %s", failure.test_name.c_str()); + NSLog(@"Failed %s — %s", failure.test_name.c_str(), failure.reason.c_str()); } } From 89743f0ba0bc230e802c6f3129b2a2b0135735fa Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 16 Oct 2023 15:40:24 -0400 Subject: [PATCH 086/128] Implement RET, IRET. --- InstructionSets/x86/Decoder.cpp | 6 +-- .../Implementation/PerformImplementation.hpp | 46 +++++++++++++++++++ OSBindings/Mac/Clock SignalTests/8088Tests.mm | 34 +++++++------- 3 files changed, 66 insertions(+), 20 deletions(-) diff --git a/InstructionSets/x86/Decoder.cpp b/InstructionSets/x86/Decoder.cpp index 56410a244..81a369e22 100644 --- a/InstructionSets/x86/Decoder.cpp +++ b/InstructionSets/x86/Decoder.cpp @@ -442,12 +442,12 @@ std::pair::InstructionT> Decoder::decode(con if constexpr (model >= Model::i80186) { Complete(LEAVE, None, None, DataSize::Byte); } else { - Complete(RETfar, None, None, DataSize::DWord); + Complete(RETfar, None, None, DataSize::Word); } break; case 0xca: RegData(RETfar, None, data_size_); break; - case 0xcb: Complete(RETfar, None, None, DataSize::DWord); break; + case 0xcb: Complete(RETfar, None, None, DataSize::Word); break; case 0xcc: // Encode INT3 as though it were INT with an @@ -1065,7 +1065,7 @@ std::pair::InstructionT> Decoder::decode(con address_size_, segment_override_, repetition_, - DataSize(operation_size_), + operation_size_, static_cast(displacement_), static_cast(operand_), consumed_ diff --git a/InstructionSets/x86/Implementation/PerformImplementation.hpp b/InstructionSets/x86/Implementation/PerformImplementation.hpp index 75f470142..735337fd2 100644 --- a/InstructionSets/x86/Implementation/PerformImplementation.hpp +++ b/InstructionSets/x86/Implementation/PerformImplementation.hpp @@ -165,6 +165,24 @@ IntT *resolve( namespace Primitive { +template +void push(IntT value, MemoryT &memory, RegistersT ®isters) { + registers.sp_ -= sizeof(IntT); + memory.template access( + InstructionSet::x86::Source::SS, + registers.sp_) = value; + memory.template write_back(); +} + +template +IntT pop(MemoryT &memory, RegistersT ®isters) { + const auto value = memory.template access( + InstructionSet::x86::Source::SS, + registers.sp_); + registers.sp_ += sizeof(IntT); + return value; +} + // // Comments below on intended functioning of each operation come from the 1997 edition of the // Intel Architecture Software Developer’s Manual; that year all such definitions still fitted within a @@ -793,6 +811,30 @@ void call_far(InstructionT &instruction, flow_controller.call(segment, offset); } +template +void iret(RegistersT ®isters, FlowControllerT &flow_controller, MemoryT &memory, Status &status) { + // TODO: all modes other than 16-bit real mode. + registers.ip() = pop(memory, registers); + registers.cs() = pop(memory, registers); + status.set(pop(memory, registers)); + flow_controller.did_iret(); +} + +template +void ret_near(InstructionT instruction, RegistersT ®isters, FlowControllerT &flow_controller, MemoryT &memory) { + registers.ip() = pop(memory, registers); + registers.sp() += instruction.operand(); + flow_controller.did_near_ret(); +} + +template +void ret_far(InstructionT instruction, RegistersT ®isters, FlowControllerT &flow_controller, MemoryT &memory) { + registers.ip() = pop(memory, registers); + registers.cs() = pop(memory, registers); + registers.sp() += instruction.operand(); + flow_controller.did_far_ret(); +} + template void ld( InstructionT &instruction, @@ -1370,6 +1412,10 @@ template < Primitive::call_far(instruction, flow_controller, registers, memory); return; + case Operation::IRET: Primitive::iret(registers, flow_controller, memory, status); return; + case Operation::RETnear: Primitive::ret_near(instruction, registers, flow_controller, memory); return; + case Operation::RETfar: Primitive::ret_far(instruction, registers, flow_controller, memory); return; + case Operation::INT: Primitive::int_(instruction.operand(), flow_controller); return; case Operation::INTO: Primitive::into(status, flow_controller); return; diff --git a/OSBindings/Mac/Clock SignalTests/8088Tests.mm b/OSBindings/Mac/Clock SignalTests/8088Tests.mm index ecbf7226d..30e21bdee 100644 --- a/OSBindings/Mac/Clock SignalTests/8088Tests.mm +++ b/OSBindings/Mac/Clock SignalTests/8088Tests.mm @@ -66,7 +66,7 @@ struct Registers { uint16_t es_, cs_, ds_, ss_; uint16_t ip_; - uint16_t ip() { return ip_; } + uint16_t &ip() { return ip_; } uint16_t &es() { return es_; } uint16_t &cs() { return cs_; } @@ -190,6 +190,10 @@ class FlowController { FlowController(Memory &memory, Registers ®isters, Status &status) : memory_(memory), registers_(registers), status_(status) {} + void did_iret() {} + void did_near_ret() {} + void did_far_ret() {} + void interrupt(int index) { const uint16_t address = static_cast(index) << 2; const uint16_t new_ip = memory_.access(address, Memory::Tag::Accessed); @@ -361,9 +365,13 @@ struct FailedExecution { // CALL @"E8.json.gz", @"FF.2.json.gz", @"9A.json.gz", @"FF.3.json.gz", +*/ + // IRET + @"CF.json.gz", - // TODO: IRET - // TODO: RET + @"C3.json.gz", @"C2.json.gz", // near RET + @"CB.json.gz", @"CA.json.gz", // far RET +/* // TODO: JMP // TODO: JCXZ @@ -379,13 +387,11 @@ struct FailedExecution { @"C5.json.gz", // LDS @"C4.json.gz", // LES @"8D.json.gz", // LEA -*/ // TODO: CMPS, LODS, MOVS, SCAS, STOS // TODO: LOOP, LOOPE, LOOPNE -/* // MOV @"88.json.gz", @"89.json.gz", @"8A.json.gz", @"8B.json.gz", @"8C.json.gz", @"8E.json.gz", @@ -434,21 +440,19 @@ struct FailedExecution { // ROR @"D0.1.json.gz", @"D2.1.json.gz", @"D1.1.json.gz", @"D3.1.json.gz", -*/ // SAL -// @"D0.4.json.gz", @"D2.4.json.gz", -// @"D1.4.json.gz", @"D3.4.json.gz", + @"D0.4.json.gz", @"D2.4.json.gz", + @"D1.4.json.gz", @"D3.4.json.gz", // SAR -// @"D0.7.json.gz", @"D2.7.json.gz", -// @"D1.7.json.gz", @"D3.7.json.gz", + @"D0.7.json.gz", @"D2.7.json.gz", + @"D1.7.json.gz", @"D3.7.json.gz", // SHR @"D0.5.json.gz", @"D2.5.json.gz", @"D1.5.json.gz", @"D3.5.json.gz", -/* @"F8.json.gz", // CLC @"FC.json.gz", // CLD @"FA.json.gz", // CLI @@ -632,7 +636,7 @@ struct FailedExecution { status.set([value[@"flags"] intValue]); } -- (void)applyExecutionTest:(NSDictionary *)test file:(NSString *)file metadata:(NSDictionary *)metadata { +- (void)applyExecutionTest:(NSDictionary *)test metadata:(NSDictionary *)metadata { InstructionSet::x86::Decoder decoder; const auto data = [self bytes:test[@"bytes"]]; const auto decoded = decoder.decode(data.data(), data.size()); @@ -656,10 +660,6 @@ struct FailedExecution { execution_support.status = initial_status; execution_support.registers = initial_registers; - if([test[@"name"] isEqual:@"rol byte ss:[bp+si+CF11h], cl"]) { - printf(""); - } - // Execute instruction. execution_support.registers.ip_ += decoded.first; InstructionSet::x86::perform( @@ -760,7 +760,7 @@ struct FailedExecution { } for(NSDictionary *test in [self testsInFile:file]) { - [self applyExecutionTest:test file:file metadata:test_metadata]; + [self applyExecutionTest:test metadata:test_metadata]; } } From 440f3bdb10a61fc421632ac8dd342b04137517af Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 16 Oct 2023 15:47:06 -0400 Subject: [PATCH 087/128] Further improve error reporting. --- InstructionSets/x86/Status.hpp | 19 +++++++++++++++++++ OSBindings/Mac/Clock SignalTests/8088Tests.mm | 6 ++++-- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/InstructionSets/x86/Status.hpp b/InstructionSets/x86/Status.hpp index 71efb2f8c..a79146200 100644 --- a/InstructionSets/x86/Status.hpp +++ b/InstructionSets/x86/Status.hpp @@ -184,6 +184,25 @@ class Status { (not_parity_bit() ? 0 : ConditionCode::Parity); } + std::string to_string() const { + std::string result; + + if(overflow) result += "O"; else result += "-"; + if(direction) result += "D"; else result += "-"; + if(interrupt) result += "I"; else result += "-"; + if(trap) result += "T"; else result += "-"; + if(sign) result += "S"; else result += "-"; + if(!zero) result += "S"; else result += "-"; + result += "-"; + if(auxiliary_carry) result += "A"; else result += "-"; + result += "-"; + if(!not_parity_bit()) result += "P"; else result += "-"; + result += "-"; + if(carry) result += "C"; else result += "-"; + + return result; + } + bool operator ==(const Status &rhs) const { return get() == rhs.get(); } diff --git a/OSBindings/Mac/Clock SignalTests/8088Tests.mm b/OSBindings/Mac/Clock SignalTests/8088Tests.mm index 30e21bdee..0f9db80f8 100644 --- a/OSBindings/Mac/Clock SignalTests/8088Tests.mm +++ b/OSBindings/Mac/Clock SignalTests/8088Tests.mm @@ -702,9 +702,11 @@ struct FailedExecution { NSMutableArray *reasons = [[NSMutableArray alloc] init]; if(!statusEqual) { + Status difference; + difference.set((intended_status.get() ^ execution_support.status.get()) & flags_mask); [reasons addObject: - [NSString stringWithFormat:@"status differs by %04x", - (intended_status.get() ^ execution_support.status.get()) & flags_mask]]; + [NSString stringWithFormat:@"status differs; errors in %s", + difference.to_string().c_str()]]; } if(!registersEqual) { [reasons addObject:@"registers don't match"]; From 90f49a6e73ca1843d63e0ae61b0be4c9418d9dbf Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 18 Oct 2023 13:15:00 -0400 Subject: [PATCH 088/128] Implement JMP. --- .../Implementation/PerformImplementation.hpp | 43 ++++++++++++++++++- OSBindings/Mac/Clock SignalTests/8088Tests.mm | 22 +++++++--- 2 files changed, 59 insertions(+), 6 deletions(-) diff --git a/InstructionSets/x86/Implementation/PerformImplementation.hpp b/InstructionSets/x86/Implementation/PerformImplementation.hpp index 735337fd2..c4d2e00a2 100644 --- a/InstructionSets/x86/Implementation/PerformImplementation.hpp +++ b/InstructionSets/x86/Implementation/PerformImplementation.hpp @@ -779,6 +779,11 @@ void call_absolute(IntT target, FlowControllerT &flow_controller) { flow_controller.call(target); } +template +void jump_absolute(IntT target, FlowControllerT &flow_controller) { + flow_controller.jump(target); +} + template void call_far(InstructionT &instruction, FlowControllerT &flow_controller, @@ -811,6 +816,38 @@ void call_far(InstructionT &instruction, flow_controller.call(segment, offset); } +template +void jump_far(InstructionT &instruction, + FlowControllerT &flow_controller, + RegistersT ®isters, + MemoryT &memory +) { + // TODO: eliminate 16-bit assumption below. + uint16_t source_address = 0; + const auto pointer = instruction.destination(); + switch(pointer.template source()) { + default: + case Source::Immediate: flow_controller.call(instruction.segment(), instruction.offset()); return; + + case Source::Indirect: + source_address = address(instruction, pointer, registers, memory); + break; + case Source::IndirectNoBase: + source_address = address(instruction, pointer, registers, memory); + break; + case Source::DirectAddress: + source_address = address(instruction, pointer, registers, memory); + break; + } + + const Source source_segment = pointer.segment(instruction.segment_override()); + + const uint16_t offset = memory.template access(source_segment, source_address); + source_address += 2; + const uint16_t segment = memory.template access(source_segment, source_address); + flow_controller.jump(segment, offset); +} + template void iret(RegistersT ®isters, FlowControllerT &flow_controller, MemoryT &memory, Status &status) { // TODO: all modes other than 16-bit real mode. @@ -1412,7 +1449,11 @@ template < Primitive::call_far(instruction, flow_controller, registers, memory); return; - case Operation::IRET: Primitive::iret(registers, flow_controller, memory, status); return; + case Operation::JMPrel: jcc(true); return; + case Operation::JMPabs: Primitive::jump_absolute(destination(), flow_controller); return; + case Operation::JMPfar: Primitive::jump_far(instruction, flow_controller, registers, memory); return; + + case Operation::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; diff --git a/OSBindings/Mac/Clock SignalTests/8088Tests.mm b/OSBindings/Mac/Clock SignalTests/8088Tests.mm index 0f9db80f8..f4228ca3b 100644 --- a/OSBindings/Mac/Clock SignalTests/8088Tests.mm +++ b/OSBindings/Mac/Clock SignalTests/8088Tests.mm @@ -214,20 +214,24 @@ class FlowController { void call(uint16_t address) { push(registers_.ip_); - registers_.ip_ = address; + jump(address); } void call(uint16_t segment, uint16_t offset) { push(registers_.cs_); push(registers_.ip_); - registers_.cs_ = segment; - registers_.ip_ = offset; + jump(segment, offset); } void jump(uint16_t address) { registers_.ip_ = address; } + void jump(uint16_t segment, uint16_t address) { + registers_.cs_ = segment; + registers_.ip_ = address; + } + void halt() {} void wait() {} @@ -365,14 +369,22 @@ struct FailedExecution { // CALL @"E8.json.gz", @"FF.2.json.gz", @"9A.json.gz", @"FF.3.json.gz", -*/ + // IRET @"CF.json.gz", @"C3.json.gz", @"C2.json.gz", // near RET @"CB.json.gz", @"CA.json.gz", // far RET +*/ + // relative JMP + @"EB.json.gz", @"E9.json.gz", + + // far JMP + @"EA.josn.gz", @"FF.5.json.gz", + + @"FF.4.json.gz", // absolute JMP + /* - // TODO: JMP // TODO: JCXZ // INTO From a8c7871b0c6f39144de9b67b3a0f874d42b43726 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 18 Oct 2023 13:20:28 -0400 Subject: [PATCH 089/128] Implement JCXZ. --- .../x86/Implementation/PerformImplementation.hpp | 4 ++++ OSBindings/Mac/Clock SignalTests/8088Tests.mm | 12 ++++-------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/InstructionSets/x86/Implementation/PerformImplementation.hpp b/InstructionSets/x86/Implementation/PerformImplementation.hpp index c4d2e00a2..fdab87e8c 100644 --- a/InstructionSets/x86/Implementation/PerformImplementation.hpp +++ b/InstructionSets/x86/Implementation/PerformImplementation.hpp @@ -1452,6 +1452,10 @@ template < case Operation::JMPrel: jcc(true); return; case Operation::JMPabs: Primitive::jump_absolute(destination(), flow_controller); return; case Operation::JMPfar: Primitive::jump_far(instruction, flow_controller, registers, memory); return; + case Operation::JCXZ: + // TODO: use ECX rather than CX if address size is 32-bit. + jcc(!registers.cx()); + return; case Operation::IRET: Primitive::iret(registers, flow_controller, memory, status); return; case Operation::RETnear: Primitive::ret_near(instruction, registers, flow_controller, memory); return; diff --git a/OSBindings/Mac/Clock SignalTests/8088Tests.mm b/OSBindings/Mac/Clock SignalTests/8088Tests.mm index f4228ca3b..85f8da4c9 100644 --- a/OSBindings/Mac/Clock SignalTests/8088Tests.mm +++ b/OSBindings/Mac/Clock SignalTests/8088Tests.mm @@ -375,18 +375,14 @@ struct FailedExecution { @"C3.json.gz", @"C2.json.gz", // near RET @"CB.json.gz", @"CA.json.gz", // far RET -*/ - // relative JMP - @"EB.json.gz", @"E9.json.gz", - // far JMP - @"EA.josn.gz", @"FF.5.json.gz", + @"EB.json.gz", @"E9.json.gz", // relative JMP + @"EA.josn.gz", @"FF.5.json.gz", // far JMP @"FF.4.json.gz", // absolute JMP - +*/ + @"E3.json.gz", // JCXZ /* - // TODO: JCXZ - // INTO @"CE.json.gz", From 02cea3047ee5691dd6511efa7344d47effefd957 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 18 Oct 2023 14:04:21 -0400 Subject: [PATCH 090/128] Implement LOOP, LOOPE, LOOPNE. --- .../Implementation/PerformImplementation.hpp | 34 ++++++++++++++++--- OSBindings/Mac/Clock SignalTests/8088Tests.mm | 34 +++++++++++++------ 2 files changed, 53 insertions(+), 15 deletions(-) diff --git a/InstructionSets/x86/Implementation/PerformImplementation.hpp b/InstructionSets/x86/Implementation/PerformImplementation.hpp index fdab87e8c..d75a848e0 100644 --- a/InstructionSets/x86/Implementation/PerformImplementation.hpp +++ b/InstructionSets/x86/Implementation/PerformImplementation.hpp @@ -674,6 +674,30 @@ void jump(bool condition, IntT displacement, RegistersT ®isters, FlowControll } } +template +void loop(IntT &counter, OffsetT displacement, RegistersT ®isters, FlowControllerT &flow_controller) { + --counter; + if(counter) { + flow_controller.jump(registers.ip() + displacement); + } +} + +template +void loope(IntT &counter, OffsetT displacement, RegistersT ®isters, Status &status, FlowControllerT &flow_controller) { + --counter; + if(counter && status.flag()) { + flow_controller.jump(registers.ip() + displacement); + } +} + +template +void loopne(IntT &counter, OffsetT displacement, RegistersT ®isters, Status &status, FlowControllerT &flow_controller) { + --counter; + if(counter && !status.flag()) { + flow_controller.jump(registers.ip() + displacement); + } +} + template void dec(IntT &destination, Status &status) { /* @@ -1452,10 +1476,12 @@ template < case Operation::JMPrel: jcc(true); return; case Operation::JMPabs: Primitive::jump_absolute(destination(), flow_controller); return; case Operation::JMPfar: Primitive::jump_far(instruction, flow_controller, registers, memory); return; - case Operation::JCXZ: - // TODO: use ECX rather than CX if address size is 32-bit. - jcc(!registers.cx()); - return; + + // TODO: use ECX rather than CX for all of below if address size is 32-bit. + case Operation::JCXZ: jcc(!registers.cx()); return; + case Operation::LOOP: Primitive::loop(registers.cx(), instruction.offset(), registers, flow_controller); return; + case Operation::LOOPE: Primitive::loope(registers.cx(), instruction.offset(), registers, status, flow_controller); return; + case Operation::LOOPNE: Primitive::loopne(registers.cx(), instruction.offset(), registers, status, flow_controller); return; case Operation::IRET: Primitive::iret(registers, flow_controller, memory, status); return; case Operation::RETnear: Primitive::ret_near(instruction, registers, flow_controller, memory); return; diff --git a/OSBindings/Mac/Clock SignalTests/8088Tests.mm b/OSBindings/Mac/Clock SignalTests/8088Tests.mm index 85f8da4c9..9c1d3129b 100644 --- a/OSBindings/Mac/Clock SignalTests/8088Tests.mm +++ b/OSBindings/Mac/Clock SignalTests/8088Tests.mm @@ -73,11 +73,6 @@ struct Registers { uint16_t &ds() { return ds_; } uint16_t &ss() { return ss_; } -// uint32_t zero_ = 0; -// template IntT &zero() { -// return static_cast(zero); -// } - bool operator ==(const Registers &rhs) const { return ax_.full == rhs.ax_.full && @@ -380,9 +375,8 @@ struct FailedExecution { @"EA.josn.gz", @"FF.5.json.gz", // far JMP @"FF.4.json.gz", // absolute JMP -*/ @"E3.json.gz", // JCXZ -/* + // INTO @"CE.json.gz", @@ -397,9 +391,11 @@ struct FailedExecution { @"8D.json.gz", // LEA // TODO: CMPS, LODS, MOVS, SCAS, STOS - - // TODO: LOOP, LOOPE, LOOPNE - +*/ + @"E0.json.gz", // LOOPNE + @"E1.json.gz", // LOOPE + @"E2.json.gz", // LOOP +/* // MOV @"88.json.gz", @"89.json.gz", @"8A.json.gz", @"8B.json.gz", @"8C.json.gz", @"8E.json.gz", @@ -717,7 +713,23 @@ struct FailedExecution { difference.to_string().c_str()]]; } if(!registersEqual) { - [reasons addObject:@"registers don't match"]; + NSMutableArray *registers = [[NSMutableArray alloc] init]; + if(intended_registers.ax() != execution_support.registers.ax()) [registers addObject:@"ax"]; + if(intended_registers.cx() != execution_support.registers.cx()) [registers addObject:@"cx"]; + if(intended_registers.dx() != execution_support.registers.dx()) [registers addObject:@"dx"]; + if(intended_registers.bx() != execution_support.registers.bx()) [registers addObject:@"bx"]; + if(intended_registers.sp() != execution_support.registers.sp()) [registers addObject:@"sp"]; + if(intended_registers.bp() != execution_support.registers.bp()) [registers addObject:@"bp"]; + if(intended_registers.si() != execution_support.registers.si()) [registers addObject:@"si"]; + if(intended_registers.di() != execution_support.registers.di()) [registers addObject:@"di"]; + if(intended_registers.ip() != execution_support.registers.ip()) [registers addObject:@"ip"]; + if(intended_registers.es() != execution_support.registers.es()) [registers addObject:@"es"]; + if(intended_registers.cs() != execution_support.registers.cs()) [registers addObject:@"cs"]; + if(intended_registers.ds() != execution_support.registers.ds()) [registers addObject:@"ds"]; + if(intended_registers.ss() != execution_support.registers.ss()) [registers addObject:@"ss"]; + [reasons addObject:[NSString stringWithFormat: + @"registers don't match: %@", [registers componentsJoinedByString:@", "] + ]]; } if(!ramEqual) { [reasons addObject:@"RAM contents don't match"]; From 617be7cba74da32dc32ef13b296df431fd9f59fa Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 18 Oct 2023 15:59:39 -0400 Subject: [PATCH 091/128] Implement PUSHes and POPs. --- .../Implementation/PerformImplementation.hpp | 22 +++++++++++++++++-- OSBindings/Mac/Clock SignalTests/8088Tests.mm | 22 ++++++++++++++++--- 2 files changed, 39 insertions(+), 5 deletions(-) diff --git a/InstructionSets/x86/Implementation/PerformImplementation.hpp b/InstructionSets/x86/Implementation/PerformImplementation.hpp index d75a848e0..1f030f771 100644 --- a/InstructionSets/x86/Implementation/PerformImplementation.hpp +++ b/InstructionSets/x86/Implementation/PerformImplementation.hpp @@ -165,8 +165,10 @@ 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. template -void push(IntT value, MemoryT &memory, RegistersT ®isters) { +void push(IntT &value, MemoryT &memory, RegistersT ®isters) { registers.sp_ -= sizeof(IntT); memory.template access( InstructionSet::x86::Source::SS, @@ -1341,6 +1343,17 @@ inline void shr(IntT &destination, uint8_t count, Status &status) { status.set_from(destination); } +template +void popf(MemoryT &memory, RegistersT ®isters, Status &status) { + status.set(pop(memory, registers)); +} + +template +void pushf(MemoryT &memory, RegistersT ®isters, Status &status) { + uint16_t value = status.get(); + push(value, memory, registers); +} + } template < @@ -1360,7 +1373,7 @@ template < [[maybe_unused]] IOT &io ) { using IntT = typename DataSizeType::type; - using AddressT = typename AddressT::type; +// using AddressT = typename AddressT::type; // Establish source() and destination() shorthand to fetch data if necessary. IntT immediate; @@ -1551,6 +1564,11 @@ template < return; case Operation::XLAT: Primitive::xlat(instruction, memory, registers); return; + + case Operation::POP: source() = Primitive::pop(memory, registers); break; + case Operation::PUSH: Primitive::push(source(), memory, registers); break; + case Operation::POPF: Primitive::popf(memory, registers, status); break; + case Operation::PUSHF: Primitive::pushf(memory, registers, status); break; } // Write to memory if required to complete this operation. diff --git a/OSBindings/Mac/Clock SignalTests/8088Tests.mm b/OSBindings/Mac/Clock SignalTests/8088Tests.mm index 9c1d3129b..ed06d0cc3 100644 --- a/OSBindings/Mac/Clock SignalTests/8088Tests.mm +++ b/OSBindings/Mac/Clock SignalTests/8088Tests.mm @@ -391,11 +391,11 @@ struct FailedExecution { @"8D.json.gz", // LEA // TODO: CMPS, LODS, MOVS, SCAS, STOS -*/ + @"E0.json.gz", // LOOPNE @"E1.json.gz", // LOOPE @"E2.json.gz", // LOOP -/* + // MOV @"88.json.gz", @"89.json.gz", @"8A.json.gz", @"8B.json.gz", @"8C.json.gz", @"8E.json.gz", @@ -426,9 +426,25 @@ struct FailedExecution { // NOP @"90.json.gz", +*/ + // POP + @"07.json.gz", @"0F.json.gz", @"17.json.gz", @"1F.json.gz", + @"58.json.gz", @"59.json.gz", @"5A.json.gz", @"5B.json.gz", + @"5C.json.gz", @"5D.json.gz", @"5E.json.gz", @"5F.json.gz", + @"8F.json.gz", - // TODO: POP, POPF, PUSH, PUSHF + // PUSH + @"06.json.gz", @"0E.json.gz", @"16.json.gz", @"1E.json.gz", + @"50.json.gz", @"51.json.gz", @"52.json.gz", @"53.json.gz", + @"54.json.gz", @"55.json.gz", @"56.json.gz", @"57.json.gz", + @"FF.6.json.gz", + // POPF + @"9D.json.gz", + + // PUSHF + @"9C.json.gz", +/* // RCL @"D0.2.json.gz", @"D2.2.json.gz", @"D1.2.json.gz", @"D3.2.json.gz", From f715cd89a9f4594af2c36c15a340d9b20a6b51ba Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 19 Oct 2023 14:07:59 -0400 Subject: [PATCH 092/128] Attempt CMPS, changing storage of direction; add flags check. --- .../Implementation/PerformImplementation.hpp | 71 +++++++++++ InstructionSets/x86/Status.hpp | 112 +++++++++--------- OSBindings/Mac/Clock SignalTests/8088Tests.mm | 36 +++++- 3 files changed, 160 insertions(+), 59 deletions(-) diff --git a/InstructionSets/x86/Implementation/PerformImplementation.hpp b/InstructionSets/x86/Implementation/PerformImplementation.hpp index 1f030f771..ae3449635 100644 --- a/InstructionSets/x86/Implementation/PerformImplementation.hpp +++ b/InstructionSets/x86/Implementation/PerformImplementation.hpp @@ -1354,6 +1354,72 @@ void pushf(MemoryT &memory, RegistersT ®isters, Status &status) { push(value, memory, registers); } +template +bool repetition_over(const InstructionT &instruction, RegistersT ®isters) { + if(instruction.repetition() == Repetition::None) { + return false; + } + + if constexpr (std::is_same_v) { + return !registers.cx(); + } else { + return !registers.ecx(); + } +} + +template +void repeat(const InstructionT &instruction, Status &status, RegistersT ®isters, FlowControllerT &flow_controller) { + if(instruction.repetition() == Repetition::None) { + return; + } + + bool count_exhausted = false; + + if constexpr (std::is_same_v) { + count_exhausted = !(--registers.cx()); + } else { + count_exhausted = !(--registers.ecx()); + } + + if(count_exhausted) { + return; + } + const bool zero = status.flag(); + if(instruction.repetition() == Repetition::RepE && !zero) { + return; + } else if(instruction.repetition() == Repetition::RepNE && zero) { + return; + } + + flow_controller.repeat_last(); +} + +template +void cmps(const InstructionT &instruction, MemoryT &memory, RegistersT ®isters, Status &status, FlowControllerT &flow_controller) { + if(repetition_over(instruction, registers)) { + return; + } + + Source source_segment = instruction.segment_override(); + if(source_segment == Source::None) source_segment = Source::DS; + + if constexpr (std::is_same_v) { + IntT source = memory.template access(source_segment, registers.si()); + IntT destination = memory.template access(Source::ES, registers.di()); + Primitive::sub(destination, source, status); + registers.si() += status.direction(); + registers.di() += status.direction(); + } else { + IntT source = memory.template access(source_segment, registers.esi()); + IntT destination = memory.template access(Source::ES, registers.edi()); + Primitive::sub(destination, source, status); + registers.esi() += status.direction(); + registers.edi() += status.direction(); + } + + repeat(instruction, status, registers, flow_controller); +} + } template < @@ -1569,6 +1635,11 @@ template < 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; + + // TODO: don't assume address size below. + case Operation::CMPS: + Primitive::cmps(instruction, memory, registers, status, flow_controller); + break; } // Write to memory if required to complete this operation. diff --git a/InstructionSets/x86/Status.hpp b/InstructionSets/x86/Status.hpp index a79146200..24797bbab 100644 --- a/InstructionSets/x86/Status.hpp +++ b/InstructionSets/x86/Status.hpp @@ -84,14 +84,14 @@ class Status { // Flag getters. template bool flag() const { switch(flag) { - case Flag::Carry: return carry; - case Flag::AuxiliaryCarry: return auxiliary_carry; - case Flag::Sign: return sign; - case Flag::Overflow: return overflow; - case Flag::Trap: return trap; - case Flag::Interrupt: return interrupt; - case Flag::Direction: return direction; - case Flag::Zero: return !zero; + case Flag::Carry: return carry_; + case Flag::AuxiliaryCarry: return auxiliary_carry_; + case Flag::Sign: return sign_; + case Flag::Overflow: return overflow_; + case Flag::Trap: return trap_; + case Flag::Interrupt: return interrupt_; + case Flag::Direction: return direction_ < 0; + case Flag::Zero: return !zero_; case Flag::ParityOdd: return not_parity_bit(); } } @@ -126,15 +126,15 @@ class Status { for(const auto flag: {flags...}) { switch(flag) { default: break; - case Flag::Zero: zero = value; break; - case Flag::Sign: sign = value & Numeric::top_bit(); break; - case Flag::ParityOdd: parity = value; break; - case Flag::Carry: carry = value; break; - case Flag::AuxiliaryCarry: auxiliary_carry = value; break; - case Flag::Overflow: overflow = value; break; - case Flag::Interrupt: interrupt = value; break; - case Flag::Trap: trap = value; break; - case Flag::Direction: direction = value; break; + case Flag::Zero: zero_ = value; break; + case Flag::Sign: sign_ = value & Numeric::top_bit(); break; + case Flag::ParityOdd: parity_ = value; break; + case Flag::Carry: carry_ = value; break; + case Flag::AuxiliaryCarry: auxiliary_carry_ = value; break; + case Flag::Overflow: overflow_ = value; break; + case Flag::Interrupt: interrupt_ = value; break; + case Flag::Trap: trap_ = value; break; + case Flag::Direction: direction_ = value ? -1 : 1; break; } } } @@ -142,44 +142,45 @@ class Status { set_from(value); } - template IntT carry_bit() const { return carry ? 1 : 0; } + template IntT carry_bit() const { return carry_ ? 1 : 0; } bool not_parity_bit() const { // x86 parity always considers the lowest 8-bits only. - auto result = static_cast(parity); + auto result = static_cast(parity_); result ^= result >> 4; result ^= result >> 2; result ^= result >> 1; return result & 1; } + template IntT direction() const { return static_cast(direction_); } + // Complete value get and set. void set(uint16_t value) { - carry = value & ConditionCode::Carry; - auxiliary_carry = value & ConditionCode::AuxiliaryCarry; - sign = value & ConditionCode::Sign; - overflow = value & ConditionCode::Overflow; - trap = value & ConditionCode::Trap; - interrupt = value & ConditionCode::Interrupt; - direction = value & ConditionCode::Direction; + carry_ = value & ConditionCode::Carry; + auxiliary_carry_ = value & ConditionCode::AuxiliaryCarry; + sign_ = value & ConditionCode::Sign; + overflow_ = value & ConditionCode::Overflow; + trap_ = value & ConditionCode::Trap; + interrupt_ = value & ConditionCode::Interrupt; + direction_ = (value & ConditionCode::Direction) ? -1 : 1; - zero = (~value) & ConditionCode::Zero; + zero_ = (~value) & ConditionCode::Zero; - parity = (~value) & ConditionCode::Parity; + parity_ = (~value) & ConditionCode::Parity; } uint16_t get() const { return 0xf002 | - (carry ? ConditionCode::Carry : 0) | - (auxiliary_carry ? ConditionCode::AuxiliaryCarry : 0) | - (sign ? ConditionCode::Sign : 0) | - (overflow ? ConditionCode::Overflow : 0) | - (trap ? ConditionCode::Trap : 0) | - (interrupt ? ConditionCode::Interrupt : 0) | - (direction ? ConditionCode::Direction : 0) | - - (zero ? 0 : ConditionCode::Zero) | + (flag() ? ConditionCode::Carry : 0) | + (flag() ? ConditionCode::AuxiliaryCarry : 0) | + (flag() ? ConditionCode::Sign : 0) | + (flag() ? ConditionCode::Overflow : 0) | + (flag() ? ConditionCode::Trap : 0) | + (flag() ? ConditionCode::Interrupt : 0) | + (flag() ? ConditionCode::Direction : 0) | + (flag() ? ConditionCode::Zero : 0) | (not_parity_bit() ? 0 : ConditionCode::Parity); } @@ -187,18 +188,18 @@ class Status { std::string to_string() const { std::string result; - if(overflow) result += "O"; else result += "-"; - if(direction) result += "D"; else result += "-"; - if(interrupt) result += "I"; else result += "-"; - if(trap) result += "T"; else result += "-"; - if(sign) result += "S"; else result += "-"; - if(!zero) result += "S"; else result += "-"; + if(overflow_) result += "O"; else result += "-"; + if(direction_) result += "D"; else result += "-"; + if(interrupt_) result += "I"; else result += "-"; + if(trap_) result += "T"; else result += "-"; + if(sign_) result += "S"; else result += "-"; + if(!zero_) result += "Z"; else result += "-"; result += "-"; - if(auxiliary_carry) result += "A"; else result += "-"; + if(auxiliary_carry_) result += "A"; else result += "-"; result += "-"; if(!not_parity_bit()) result += "P"; else result += "-"; result += "-"; - if(carry) result += "C"; else result += "-"; + if(carry_) result += "C"; else result += "-"; return result; } @@ -209,19 +210,22 @@ class Status { private: // Non-zero => set; zero => unset. - uint32_t carry; - uint32_t auxiliary_carry; - uint32_t sign; - uint32_t overflow; - uint32_t trap; - uint32_t interrupt; - uint32_t direction; + uint32_t carry_; + uint32_t auxiliary_carry_; + uint32_t sign_; + uint32_t overflow_; + uint32_t trap_; + uint32_t interrupt_; + + // +1 = direction flag not set; + // -1 = direction flag set. + int32_t direction_; // Zero => set; non-zero => unset. - uint32_t zero; + uint32_t zero_; // Odd number of bits => set; even => unset. - uint32_t parity; + uint32_t parity_; }; } diff --git a/OSBindings/Mac/Clock SignalTests/8088Tests.mm b/OSBindings/Mac/Clock SignalTests/8088Tests.mm index ed06d0cc3..fa3adf2dd 100644 --- a/OSBindings/Mac/Clock SignalTests/8088Tests.mm +++ b/OSBindings/Mac/Clock SignalTests/8088Tests.mm @@ -230,6 +230,10 @@ class FlowController { void halt() {} void wait() {} + void repeat_last() { + // TODO. + } + private: Memory &memory_; Registers ®isters_; @@ -390,8 +394,11 @@ struct FailedExecution { @"C4.json.gz", // LES @"8D.json.gz", // LEA - // TODO: CMPS, LODS, MOVS, SCAS, STOS - + // TODO: LODS, MOVS, SCAS, STOS +*/ + // CMPS + @"A6.json.gz", //@"A7.json.gz", +/* @"E0.json.gz", // LOOPNE @"E1.json.gz", // LOOPE @"E2.json.gz", // LOOP @@ -426,7 +433,7 @@ struct FailedExecution { // NOP @"90.json.gz", -*/ + // POP @"07.json.gz", @"0F.json.gz", @"17.json.gz", @"1F.json.gz", @"58.json.gz", @"59.json.gz", @"5A.json.gz", @"5B.json.gz", @@ -444,7 +451,7 @@ struct FailedExecution { // PUSHF @"9C.json.gz", -/* + // RCL @"D0.2.json.gz", @"D2.2.json.gz", @"D1.2.json.gz", @"D3.2.json.gz", @@ -653,7 +660,26 @@ struct FailedExecution { registers.ss_ = [value[@"ss"] intValue]; registers.ip_ = [value[@"ip"] intValue]; - status.set([value[@"flags"] intValue]); + const uint16_t flags = [value[@"flags"] intValue]; + status.set(flags); + + // Apply a quick test of flag packing/unpacking. + constexpr auto defined_flags = static_cast( + InstructionSet::x86::ConditionCode::Carry | + InstructionSet::x86::ConditionCode::Parity | + InstructionSet::x86::ConditionCode::AuxiliaryCarry | + InstructionSet::x86::ConditionCode::Zero | + InstructionSet::x86::ConditionCode::Sign | + InstructionSet::x86::ConditionCode::Trap | + InstructionSet::x86::ConditionCode::Interrupt | + InstructionSet::x86::ConditionCode::Direction | + InstructionSet::x86::ConditionCode::Overflow + ); + XCTAssert((status.get() & defined_flags) == (flags & defined_flags), + "Set status of %04x was returned as %04x", + flags & defined_flags, + (status.get() & defined_flags) + ); } - (void)applyExecutionTest:(NSDictionary *)test metadata:(NSDictionary *)metadata { From 387a952328ab407cc6c31dc7887513adf302bae8 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 19 Oct 2023 14:21:08 -0400 Subject: [PATCH 093/128] Reduce repetition. --- InstructionSets/x86/Status.hpp | 38 +++++++++++++++++----------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/InstructionSets/x86/Status.hpp b/InstructionSets/x86/Status.hpp index 24797bbab..9e6f77ffc 100644 --- a/InstructionSets/x86/Status.hpp +++ b/InstructionSets/x86/Status.hpp @@ -156,17 +156,17 @@ class Status { // Complete value get and set. void set(uint16_t value) { - carry_ = value & ConditionCode::Carry; - auxiliary_carry_ = value & ConditionCode::AuxiliaryCarry; - sign_ = value & ConditionCode::Sign; - overflow_ = value & ConditionCode::Overflow; - trap_ = value & ConditionCode::Trap; - interrupt_ = value & ConditionCode::Interrupt; - direction_ = (value & ConditionCode::Direction) ? -1 : 1; + set_from(value & ConditionCode::Carry); + set_from(value & ConditionCode::AuxiliaryCarry); + set_from(value & ConditionCode::Overflow); + set_from(value & ConditionCode::Trap); + set_from(value & ConditionCode::Interrupt); + set_from(value & ConditionCode::Direction); - zero_ = (~value) & ConditionCode::Zero; + set_from(value); - parity_ = (~value) & ConditionCode::Parity; + set_from((~value) & ConditionCode::Zero); + set_from((~value) & ConditionCode::Parity); } uint16_t get() const { @@ -182,24 +182,24 @@ class Status { (flag() ? ConditionCode::Direction : 0) | (flag() ? ConditionCode::Zero : 0) | - (not_parity_bit() ? 0 : ConditionCode::Parity); + (flag() ? 0 : ConditionCode::Parity); } std::string to_string() const { std::string result; - if(overflow_) result += "O"; else result += "-"; - if(direction_) result += "D"; else result += "-"; - if(interrupt_) result += "I"; else result += "-"; - if(trap_) result += "T"; else result += "-"; - if(sign_) result += "S"; else result += "-"; - if(!zero_) result += "Z"; else result += "-"; + if(flag()) result += "O"; else result += "-"; + if(flag()) result += "D"; else result += "-"; + if(flag()) result += "I"; else result += "-"; + if(flag()) result += "T"; else result += "-"; + if(flag()) result += "S"; else result += "-"; + if(flag()) result += "Z"; else result += "-"; result += "-"; - if(auxiliary_carry_) result += "A"; else result += "-"; + if(flag()) result += "A"; else result += "-"; result += "-"; - if(!not_parity_bit()) result += "P"; else result += "-"; + if(!flag()) result += "P"; else result += "-"; result += "-"; - if(carry_) result += "C"; else result += "-"; + if(flag()) result += "C"; else result += "-"; return result; } From efb854ddfa0556affdaae2a9a242c1dad2942275 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 19 Oct 2023 14:40:03 -0400 Subject: [PATCH 094/128] Fix repetition. Sufficient for tests. --- .../Implementation/PerformImplementation.hpp | 18 +++++++----- OSBindings/Mac/Clock SignalTests/8088Tests.mm | 28 +++++++++++++------ 2 files changed, 30 insertions(+), 16 deletions(-) diff --git a/InstructionSets/x86/Implementation/PerformImplementation.hpp b/InstructionSets/x86/Implementation/PerformImplementation.hpp index ae3449635..9f65125b8 100644 --- a/InstructionSets/x86/Implementation/PerformImplementation.hpp +++ b/InstructionSets/x86/Implementation/PerformImplementation.hpp @@ -1401,18 +1401,22 @@ void cmps(const InstructionT &instruction, MemoryT &memory, RegistersT ®ister } Source source_segment = instruction.segment_override(); - if(source_segment == Source::None) source_segment = Source::DS; + if(source_segment == Source::None) { + source_segment = Source::DS; + } else { + printf(""); + } if constexpr (std::is_same_v) { - IntT source = memory.template access(source_segment, registers.si()); - IntT destination = memory.template access(Source::ES, registers.di()); - Primitive::sub(destination, source, status); + IntT lhs = memory.template access(source_segment, registers.si()); + IntT rhs = memory.template access(Source::ES, registers.di()); + Primitive::sub(lhs, rhs, status); registers.si() += status.direction(); registers.di() += status.direction(); } else { - IntT source = memory.template access(source_segment, registers.esi()); - IntT destination = memory.template access(Source::ES, registers.edi()); - Primitive::sub(destination, source, status); + IntT lhs = memory.template access(source_segment, registers.esi()); + IntT rhs = memory.template access(Source::ES, registers.edi()); + Primitive::sub(lhs, rhs, status); registers.esi() += status.direction(); registers.edi() += status.direction(); } diff --git a/OSBindings/Mac/Clock SignalTests/8088Tests.mm b/OSBindings/Mac/Clock SignalTests/8088Tests.mm index fa3adf2dd..3c83b3602 100644 --- a/OSBindings/Mac/Clock SignalTests/8088Tests.mm +++ b/OSBindings/Mac/Clock SignalTests/8088Tests.mm @@ -230,14 +230,21 @@ class FlowController { void halt() {} void wait() {} + void begin_instruction() { + should_repeat_ = false; + } void repeat_last() { - // TODO. + should_repeat_ = true; + } + bool should_repeat() const { + return should_repeat_; } private: Memory &memory_; Registers ®isters_; Status &status_; + bool should_repeat_ = false; void push(uint16_t value, bool is_flags = false) { // Perform the push in two steps because it's possible for SP to underflow, and so that FlagsL and @@ -708,14 +715,17 @@ struct FailedExecution { // Execute instruction. execution_support.registers.ip_ += decoded.first; - InstructionSet::x86::perform( - decoded.second, - execution_support.status, - execution_support.flow_controller, - execution_support.registers, - execution_support.memory, - execution_support.io - ); + do { + execution_support.flow_controller.begin_instruction(); + InstructionSet::x86::perform( + decoded.second, + execution_support.status, + execution_support.flow_controller, + execution_support.registers, + execution_support.memory, + execution_support.io + ); + } while (execution_support.flow_controller.should_repeat()); // Compare final state. Registers intended_registers; From a71db54212d628cd6fd299b2fa110c3a8abae794 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 20 Oct 2023 16:52:47 -0400 Subject: [PATCH 095/128] Simplify flow slightly; uncover issues in CMPSW. --- .../Implementation/PerformImplementation.hpp | 48 +++++++++++-------- OSBindings/Mac/Clock SignalTests/8088Tests.mm | 5 +- 2 files changed, 31 insertions(+), 22 deletions(-) diff --git a/InstructionSets/x86/Implementation/PerformImplementation.hpp b/InstructionSets/x86/Implementation/PerformImplementation.hpp index 9f65125b8..fd0360d75 100644 --- a/InstructionSets/x86/Implementation/PerformImplementation.hpp +++ b/InstructionSets/x86/Implementation/PerformImplementation.hpp @@ -1395,31 +1395,20 @@ void repeat(const InstructionT &instruction, Status &status, RegistersT ®iste } template -void cmps(const InstructionT &instruction, MemoryT &memory, RegistersT ®isters, Status &status, FlowControllerT &flow_controller) { +void cmps(const InstructionT &instruction, AddressT &eSI, AddressT &eDI, MemoryT &memory, RegistersT ®isters, Status &status, FlowControllerT &flow_controller) { if(repetition_over(instruction, registers)) { return; } Source source_segment = instruction.segment_override(); - if(source_segment == Source::None) { - source_segment = Source::DS; - } else { - printf(""); - } + if(source_segment == Source::None) source_segment = Source::DS; - if constexpr (std::is_same_v) { - IntT lhs = memory.template access(source_segment, registers.si()); - IntT rhs = memory.template access(Source::ES, registers.di()); - Primitive::sub(lhs, rhs, status); - registers.si() += status.direction(); - registers.di() += status.direction(); - } else { - IntT lhs = memory.template access(source_segment, registers.esi()); - IntT rhs = memory.template access(Source::ES, registers.edi()); - Primitive::sub(lhs, rhs, status); - registers.esi() += status.direction(); - registers.edi() += status.direction(); - } + IntT lhs = memory.template access(source_segment, eSI); + const IntT rhs = memory.template access(Source::ES, eDI); + eSI += status.direction(); + eDI += status.direction(); + + Primitive::sub(lhs, rhs, status); repeat(instruction, status, registers, flow_controller); } @@ -1443,7 +1432,7 @@ template < [[maybe_unused]] IOT &io ) { using IntT = typename DataSizeType::type; -// using AddressT = typename AddressT::type; + using AddressT = uint16_t; // TODO. // Establish source() and destination() shorthand to fetch data if necessary. IntT immediate; @@ -1500,6 +1489,23 @@ template < else if constexpr (data_size == DataSize::DWord) return registers.eax(); }; + // For the string versions, evaluate to either SI and DI or ESI and EDI, depending on the address size. + // [TODO on the latter]. + const auto eSI = [&]() -> AddressT& { + if constexpr (std::is_same_v) { + return registers.si(); + } else { + return registers.esi(); + } + }; + const auto eDI = [&]() -> AddressT& { + if constexpr (std::is_same_v) { + return registers.di(); + } else { + return registers.edi(); + } + }; + // Guide to the below: // // * use hard-coded register names where appropriate; @@ -1642,7 +1648,7 @@ template < // TODO: don't assume address size below. case Operation::CMPS: - Primitive::cmps(instruction, memory, registers, status, flow_controller); + Primitive::cmps(instruction, eSI(), eDI(), memory, registers, status, flow_controller); break; } diff --git a/OSBindings/Mac/Clock SignalTests/8088Tests.mm b/OSBindings/Mac/Clock SignalTests/8088Tests.mm index 3c83b3602..91f487653 100644 --- a/OSBindings/Mac/Clock SignalTests/8088Tests.mm +++ b/OSBindings/Mac/Clock SignalTests/8088Tests.mm @@ -404,7 +404,7 @@ struct FailedExecution { // TODO: LODS, MOVS, SCAS, STOS */ // CMPS - @"A6.json.gz", //@"A7.json.gz", + @"A6.json.gz", @"A7.json.gz", /* @"E0.json.gz", // LOOPNE @"E1.json.gz", // LOOPE @@ -714,6 +714,9 @@ struct FailedExecution { execution_support.registers = initial_registers; // Execute instruction. + // + // TODO: enquire of the actual mechanism of repetition; if it were stateful as below then + // would it survive interrupts? So is it just IP adjustment? execution_support.registers.ip_ += decoded.first; do { execution_support.flow_controller.begin_instruction(); From 49ac2d8e0ce289a46826179ea7ccb543522b0808 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 20 Oct 2023 17:00:32 -0400 Subject: [PATCH 096/128] Improve error reporting, remove some dead TODOs. --- .../Implementation/PerformImplementation.hpp | 26 ++++++++------ OSBindings/Mac/Clock SignalTests/8088Tests.mm | 34 ++++++++++++------- 2 files changed, 37 insertions(+), 23 deletions(-) diff --git a/InstructionSets/x86/Implementation/PerformImplementation.hpp b/InstructionSets/x86/Implementation/PerformImplementation.hpp index fd0360d75..ecabc8949 100644 --- a/InstructionSets/x86/Implementation/PerformImplementation.hpp +++ b/InstructionSets/x86/Implementation/PerformImplementation.hpp @@ -1489,8 +1489,7 @@ template < else if constexpr (data_size == DataSize::DWord) return registers.eax(); }; - // For the string versions, evaluate to either SI and DI or ESI and EDI, depending on the address size. - // [TODO on the latter]. + // 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(); @@ -1506,6 +1505,15 @@ template < } }; + // For counts, provide either eCX or CX depending on address size. + const auto eCX = [&]() -> AddressT& { + if constexpr (std::is_same_v) { + return registers.cx(); + } else { + return registers.ecx(); + } + }; + // Guide to the below: // // * use hard-coded register names where appropriate; @@ -1566,11 +1574,10 @@ template < case Operation::JMPabs: Primitive::jump_absolute(destination(), flow_controller); return; case Operation::JMPfar: Primitive::jump_far(instruction, flow_controller, registers, memory); return; - // TODO: use ECX rather than CX for all of below if address size is 32-bit. - case Operation::JCXZ: jcc(!registers.cx()); return; - case Operation::LOOP: Primitive::loop(registers.cx(), instruction.offset(), registers, flow_controller); return; - case Operation::LOOPE: Primitive::loope(registers.cx(), instruction.offset(), registers, status, flow_controller); return; - case Operation::LOOPNE: Primitive::loopne(registers.cx(), instruction.offset(), registers, status, flow_controller); return; + case Operation::JCXZ: jcc(!eCX()); return; + case Operation::LOOP: Primitive::loop(eCX(), instruction.offset(), registers, flow_controller); return; + case Operation::LOOPE: Primitive::loope(eCX(), instruction.offset(), registers, status, flow_controller); return; + case Operation::LOOPNE: Primitive::loopne(eCX(), instruction.offset(), registers, status, flow_controller); return; case Operation::IRET: Primitive::iret(registers, flow_controller, memory, status); return; case Operation::RETnear: Primitive::ret_near(instruction, registers, flow_controller, memory); return; @@ -1639,16 +1646,15 @@ template < } return; - case Operation::XLAT: Primitive::xlat(instruction, memory, registers); return; + case Operation::XLAT: Primitive::xlat(instruction, memory, registers); return; case Operation::POP: source() = Primitive::pop(memory, registers); break; case Operation::PUSH: Primitive::push(source(), memory, registers); break; case Operation::POPF: Primitive::popf(memory, registers, status); break; case Operation::PUSHF: Primitive::pushf(memory, registers, status); break; - // TODO: don't assume address size below. case Operation::CMPS: - Primitive::cmps(instruction, eSI(), eDI(), memory, registers, status, flow_controller); + Primitive::cmps(instruction, eSI(), eDI(), memory, registers, status, flow_controller); break; } diff --git a/OSBindings/Mac/Clock SignalTests/8088Tests.mm b/OSBindings/Mac/Clock SignalTests/8088Tests.mm index 91f487653..fc196152f 100644 --- a/OSBindings/Mac/Clock SignalTests/8088Tests.mm +++ b/OSBindings/Mac/Clock SignalTests/8088Tests.mm @@ -769,19 +769,27 @@ struct FailedExecution { } if(!registersEqual) { NSMutableArray *registers = [[NSMutableArray alloc] init]; - if(intended_registers.ax() != execution_support.registers.ax()) [registers addObject:@"ax"]; - if(intended_registers.cx() != execution_support.registers.cx()) [registers addObject:@"cx"]; - if(intended_registers.dx() != execution_support.registers.dx()) [registers addObject:@"dx"]; - if(intended_registers.bx() != execution_support.registers.bx()) [registers addObject:@"bx"]; - if(intended_registers.sp() != execution_support.registers.sp()) [registers addObject:@"sp"]; - if(intended_registers.bp() != execution_support.registers.bp()) [registers addObject:@"bp"]; - if(intended_registers.si() != execution_support.registers.si()) [registers addObject:@"si"]; - if(intended_registers.di() != execution_support.registers.di()) [registers addObject:@"di"]; - if(intended_registers.ip() != execution_support.registers.ip()) [registers addObject:@"ip"]; - if(intended_registers.es() != execution_support.registers.es()) [registers addObject:@"es"]; - if(intended_registers.cs() != execution_support.registers.cs()) [registers addObject:@"cs"]; - if(intended_registers.ds() != execution_support.registers.ds()) [registers addObject:@"ds"]; - if(intended_registers.ss() != execution_support.registers.ss()) [registers addObject:@"ss"]; +#define Reg(x) \ + if(intended_registers.x() != execution_support.registers.x()) \ + [registers addObject: \ + [NSString stringWithFormat: \ + @#x" is %04x rather than %04x", execution_support.registers.x(), intended_registers.x()]]; + + Reg(ax); + Reg(cx); + Reg(dx); + Reg(bx); + Reg(sp); + Reg(bp); + Reg(si); + Reg(di); + Reg(ip); + Reg(es); + Reg(cs); + Reg(ds); + Reg(ss); + +#undef Reg [reasons addObject:[NSString stringWithFormat: @"registers don't match: %@", [registers componentsJoinedByString:@", "] ]]; From 0f5e0e17a430a115e2717c11286e83fb50ce9ce8 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 20 Oct 2023 17:03:23 -0400 Subject: [PATCH 097/128] Fix address manipulation. --- InstructionSets/x86/Implementation/PerformImplementation.hpp | 4 ++-- OSBindings/Mac/Clock SignalTests/8088Tests.mm | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/InstructionSets/x86/Implementation/PerformImplementation.hpp b/InstructionSets/x86/Implementation/PerformImplementation.hpp index ecabc8949..0a5297ac5 100644 --- a/InstructionSets/x86/Implementation/PerformImplementation.hpp +++ b/InstructionSets/x86/Implementation/PerformImplementation.hpp @@ -1405,8 +1405,8 @@ void cmps(const InstructionT &instruction, AddressT &eSI, AddressT &eDI, MemoryT IntT lhs = memory.template access(source_segment, eSI); const IntT rhs = memory.template access(Source::ES, eDI); - eSI += status.direction(); - eDI += status.direction(); + eSI += status.direction() * sizeof(IntT); + eDI += status.direction() * sizeof(IntT); Primitive::sub(lhs, rhs, status); diff --git a/OSBindings/Mac/Clock SignalTests/8088Tests.mm b/OSBindings/Mac/Clock SignalTests/8088Tests.mm index fc196152f..aabff09fd 100644 --- a/OSBindings/Mac/Clock SignalTests/8088Tests.mm +++ b/OSBindings/Mac/Clock SignalTests/8088Tests.mm @@ -404,7 +404,7 @@ struct FailedExecution { // TODO: LODS, MOVS, SCAS, STOS */ // CMPS - @"A6.json.gz", @"A7.json.gz", + @"A6.json.gz", @"A7.json.gz", /* @"E0.json.gz", // LOOPNE @"E1.json.gz", // LOOPE @@ -844,8 +844,10 @@ struct FailedExecution { test_metadata = test_metadata[@"reg"][[NSString stringWithFormat:@"%c", [name characterAtIndex:first_dot.location+1]]]; } + int index = 0; for(NSDictionary *test in [self testsInFile:file]) { [self applyExecutionTest:test metadata:test_metadata]; + ++index; } } From bcebb2e52036bf4a69ceefb5b0d009066fb3c991 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 20 Oct 2023 17:08:11 -0400 Subject: [PATCH 098/128] Further reduce repetition overhead. --- .../Implementation/PerformImplementation.hpp | 55 ++++++------------- 1 file changed, 16 insertions(+), 39 deletions(-) diff --git a/InstructionSets/x86/Implementation/PerformImplementation.hpp b/InstructionSets/x86/Implementation/PerformImplementation.hpp index 0a5297ac5..357be1fa7 100644 --- a/InstructionSets/x86/Implementation/PerformImplementation.hpp +++ b/InstructionSets/x86/Implementation/PerformImplementation.hpp @@ -1354,49 +1354,26 @@ void pushf(MemoryT &memory, RegistersT ®isters, Status &status) { push(value, memory, registers); } -template -bool repetition_over(const InstructionT &instruction, RegistersT ®isters) { - if(instruction.repetition() == Repetition::None) { - return false; - } - - if constexpr (std::is_same_v) { - return !registers.cx(); - } else { - return !registers.ecx(); - } +template +bool repetition_over(const InstructionT &instruction, AddressT &eCX) { + return instruction.repetition() != Repetition::None && !eCX; } -template -void repeat(const InstructionT &instruction, Status &status, RegistersT ®isters, FlowControllerT &flow_controller) { - if(instruction.repetition() == Repetition::None) { +template +void repeat(const InstructionT &instruction, Status &status, AddressT &eCX, FlowControllerT &flow_controller) { + if( + instruction.repetition() == Repetition::None || + !(--eCX) || + (instruction.repetition() == Repetition::RepNE) == status.flag() + ) { return; } - - bool count_exhausted = false; - - if constexpr (std::is_same_v) { - count_exhausted = !(--registers.cx()); - } else { - count_exhausted = !(--registers.ecx()); - } - - if(count_exhausted) { - return; - } - const bool zero = status.flag(); - if(instruction.repetition() == Repetition::RepE && !zero) { - return; - } else if(instruction.repetition() == Repetition::RepNE && zero) { - return; - } - flow_controller.repeat_last(); } -template -void cmps(const InstructionT &instruction, AddressT &eSI, AddressT &eDI, MemoryT &memory, RegistersT ®isters, Status &status, FlowControllerT &flow_controller) { - if(repetition_over(instruction, registers)) { +template +void cmps(const InstructionT &instruction, AddressT &eCX, AddressT &eSI, AddressT &eDI, MemoryT &memory, Status &status, FlowControllerT &flow_controller) { + if(repetition_over(instruction, eCX)) { return; } @@ -1410,7 +1387,7 @@ void cmps(const InstructionT &instruction, AddressT &eSI, AddressT &eDI, MemoryT Primitive::sub(lhs, rhs, status); - repeat(instruction, status, registers, flow_controller); + repeat(instruction, status, eCX, flow_controller); } } @@ -1630,7 +1607,7 @@ template < case Operation::XCHG: Primitive::xchg(destination(), source()); return; - 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); @@ -1654,7 +1631,7 @@ template < case Operation::PUSHF: Primitive::pushf(memory, registers, status); break; case Operation::CMPS: - Primitive::cmps(instruction, eSI(), eDI(), memory, registers, status, flow_controller); + Primitive::cmps(instruction, eCX(), eSI(), eDI(), memory, status, flow_controller); break; } From bee094eba156565f34b36feca33b73f69f5a268f Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 20 Oct 2023 17:13:56 -0400 Subject: [PATCH 099/128] Add LODS; somehow manage to fail some of its tests. --- .../Implementation/PerformImplementation.hpp | 19 +++++++++++++++++++ OSBindings/Mac/Clock SignalTests/8088Tests.mm | 5 ++++- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/InstructionSets/x86/Implementation/PerformImplementation.hpp b/InstructionSets/x86/Implementation/PerformImplementation.hpp index 357be1fa7..f79aba401 100644 --- a/InstructionSets/x86/Implementation/PerformImplementation.hpp +++ b/InstructionSets/x86/Implementation/PerformImplementation.hpp @@ -1390,6 +1390,22 @@ void cmps(const InstructionT &instruction, AddressT &eCX, AddressT &eSI, Address repeat(instruction, status, eCX, flow_controller); } +template +void lods(const InstructionT &instruction, AddressT &eCX, AddressT &eSI, IntT &eAX, MemoryT &memory, Status &status, FlowControllerT &flow_controller) { + if(repetition_over(instruction, eCX)) { + return; + } + + Source source_segment = instruction.segment_override(); + if(source_segment == Source::None) source_segment = Source::DS; + + eAX = memory.template access(source_segment, eSI); + eSI += status.direction() * sizeof(IntT); + + repeat(instruction, status, eCX, flow_controller); +} + + } template < @@ -1633,6 +1649,9 @@ template < case Operation::CMPS: Primitive::cmps(instruction, eCX(), eSI(), eDI(), memory, status, flow_controller); break; + case Operation::LODS: + Primitive::lods(instruction, eCX(), eSI(), pair_low(), memory, status, flow_controller); + break; } // Write to memory if required to complete this operation. diff --git a/OSBindings/Mac/Clock SignalTests/8088Tests.mm b/OSBindings/Mac/Clock SignalTests/8088Tests.mm index aabff09fd..a555f6ec0 100644 --- a/OSBindings/Mac/Clock SignalTests/8088Tests.mm +++ b/OSBindings/Mac/Clock SignalTests/8088Tests.mm @@ -401,10 +401,13 @@ struct FailedExecution { @"C4.json.gz", // LES @"8D.json.gz", // LEA - // TODO: LODS, MOVS, SCAS, STOS + // TODO: MOVS, SCAS, STOS */ // CMPS @"A6.json.gz", @"A7.json.gz", + + // LODS + @"AC.json.gz", @"AD.json.gz", /* @"E0.json.gz", // LOOPNE @"E1.json.gz", // LOOPE From 8caad8b99d739b87872145bccd2229295cecb2a1 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 20 Oct 2023 17:25:27 -0400 Subject: [PATCH 100/128] Document slightly. --- InstructionSets/x86/Implementation/PerformImplementation.hpp | 5 +++-- OSBindings/Mac/Clock SignalTests/8088Tests.mm | 5 ++++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/InstructionSets/x86/Implementation/PerformImplementation.hpp b/InstructionSets/x86/Implementation/PerformImplementation.hpp index f79aba401..9d71babe2 100644 --- a/InstructionSets/x86/Implementation/PerformImplementation.hpp +++ b/InstructionSets/x86/Implementation/PerformImplementation.hpp @@ -1362,9 +1362,10 @@ bool repetition_over(const InstructionT &instruction, AddressT &eCX) { template void repeat(const InstructionT &instruction, Status &status, AddressT &eCX, FlowControllerT &flow_controller) { if( - instruction.repetition() == Repetition::None || - !(--eCX) || + instruction.repetition() == Repetition::None || // No repetition => stop. + !(--eCX) || // [e]cx is zero after being decremented => stop. (instruction.repetition() == Repetition::RepNE) == status.flag() + // repe and !zero, or repne and zero => stop. ) { return; } diff --git a/OSBindings/Mac/Clock SignalTests/8088Tests.mm b/OSBindings/Mac/Clock SignalTests/8088Tests.mm index a555f6ec0..629e99a1e 100644 --- a/OSBindings/Mac/Clock SignalTests/8088Tests.mm +++ b/OSBindings/Mac/Clock SignalTests/8088Tests.mm @@ -404,7 +404,7 @@ struct FailedExecution { // TODO: MOVS, SCAS, STOS */ // CMPS - @"A6.json.gz", @"A7.json.gz", +// @"A6.json.gz", @"A7.json.gz", // LODS @"AC.json.gz", @"AD.json.gz", @@ -849,6 +849,9 @@ struct FailedExecution { int index = 0; for(NSDictionary *test in [self testsInFile:file]) { + if(index == 10) { + printf(""); + } [self applyExecutionTest:test metadata:test_metadata]; ++index; } From dab3dcaafb7626bc5a659124b57cb7af2296f422 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 20 Oct 2023 21:36:50 -0400 Subject: [PATCH 101/128] Fix LODS: REP is not REPE. --- .../Implementation/PerformImplementation.hpp | 17 ++++++++++++++--- OSBindings/Mac/Clock SignalTests/8088Tests.mm | 2 +- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/InstructionSets/x86/Implementation/PerformImplementation.hpp b/InstructionSets/x86/Implementation/PerformImplementation.hpp index 9d71babe2..1160b2aaf 100644 --- a/InstructionSets/x86/Implementation/PerformImplementation.hpp +++ b/InstructionSets/x86/Implementation/PerformImplementation.hpp @@ -1360,7 +1360,7 @@ bool repetition_over(const InstructionT &instruction, AddressT &eCX) { } template -void repeat(const InstructionT &instruction, Status &status, AddressT &eCX, FlowControllerT &flow_controller) { +void repeat_ene(const InstructionT &instruction, Status &status, AddressT &eCX, FlowControllerT &flow_controller) { if( instruction.repetition() == Repetition::None || // No repetition => stop. !(--eCX) || // [e]cx is zero after being decremented => stop. @@ -1372,6 +1372,17 @@ void repeat(const InstructionT &instruction, Status &status, AddressT &eCX, Flow flow_controller.repeat_last(); } +template +void repeat(const InstructionT &instruction, AddressT &eCX, FlowControllerT &flow_controller) { + if( + instruction.repetition() == Repetition::None || // No repetition => stop. + !(--eCX) // [e]cx is zero after being decremented => stop. + ) { + return; + } + flow_controller.repeat_last(); +} + template void cmps(const InstructionT &instruction, AddressT &eCX, AddressT &eSI, AddressT &eDI, MemoryT &memory, Status &status, FlowControllerT &flow_controller) { if(repetition_over(instruction, eCX)) { @@ -1388,7 +1399,7 @@ void cmps(const InstructionT &instruction, AddressT &eCX, AddressT &eSI, Address Primitive::sub(lhs, rhs, status); - repeat(instruction, status, eCX, flow_controller); + repeat_ene(instruction, status, eCX, flow_controller); } template @@ -1403,7 +1414,7 @@ void lods(const InstructionT &instruction, AddressT &eCX, AddressT &eSI, IntT &e eAX = memory.template access(source_segment, eSI); eSI += status.direction() * sizeof(IntT); - repeat(instruction, status, eCX, flow_controller); + repeat(instruction, eCX, flow_controller); } diff --git a/OSBindings/Mac/Clock SignalTests/8088Tests.mm b/OSBindings/Mac/Clock SignalTests/8088Tests.mm index 629e99a1e..b4086eff4 100644 --- a/OSBindings/Mac/Clock SignalTests/8088Tests.mm +++ b/OSBindings/Mac/Clock SignalTests/8088Tests.mm @@ -404,7 +404,7 @@ struct FailedExecution { // TODO: MOVS, SCAS, STOS */ // CMPS -// @"A6.json.gz", @"A7.json.gz", + @"A6.json.gz", @"A7.json.gz", // LODS @"AC.json.gz", @"AD.json.gz", From 93e90b09a01a4da09057d1c39110673d1ff15a3b Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 20 Oct 2023 21:46:47 -0400 Subject: [PATCH 102/128] Implement MOVS, STOS, revealing an issue in the memory handler. --- .../Implementation/PerformImplementation.hpp | 38 +++++++++++++++++++ OSBindings/Mac/Clock SignalTests/8088Tests.mm | 8 +++- 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/InstructionSets/x86/Implementation/PerformImplementation.hpp b/InstructionSets/x86/Implementation/PerformImplementation.hpp index 1160b2aaf..57343b294 100644 --- a/InstructionSets/x86/Implementation/PerformImplementation.hpp +++ b/InstructionSets/x86/Implementation/PerformImplementation.hpp @@ -1417,6 +1417,38 @@ void lods(const InstructionT &instruction, AddressT &eCX, AddressT &eSI, IntT &e repeat(instruction, eCX, flow_controller); } +template +void movs(const InstructionT &instruction, AddressT &eCX, AddressT &eSI, AddressT &eDI, MemoryT &memory, Status &status, FlowControllerT &flow_controller) { + if(repetition_over(instruction, eCX)) { + return; + } + + Source source_segment = instruction.segment_override(); + if(source_segment == Source::None) source_segment = Source::DS; + + memory.template access(Source::ES, eDI) = memory.template access(source_segment, eSI); + + eSI += status.direction() * sizeof(IntT); + eDI += status.direction() * sizeof(IntT); + + repeat(instruction, eCX, flow_controller); +} + +template +void stos(const InstructionT &instruction, AddressT &eCX, AddressT &eDI, IntT &eAX, MemoryT &memory, Status &status, FlowControllerT &flow_controller) { + if(repetition_over(instruction, eCX)) { + return; + } + + Source destination_segment = instruction.segment_override(); + if(destination_segment == Source::None) destination_segment = Source::DS; + + memory.template access(destination_segment, eDI) = eAX; + + eDI += status.direction() * sizeof(IntT); + + repeat(instruction, eCX, flow_controller); +} } @@ -1664,6 +1696,12 @@ template < case Operation::LODS: Primitive::lods(instruction, eCX(), eSI(), pair_low(), memory, status, flow_controller); break; + case Operation::MOVS: + Primitive::movs(instruction, eCX(), eSI(), eDI(), memory, status, flow_controller); + break; + case Operation::STOS: + Primitive::stos(instruction, eCX(), eDI(), pair_low(), memory, status, flow_controller); + break; } // Write to memory if required to complete this operation. diff --git a/OSBindings/Mac/Clock SignalTests/8088Tests.mm b/OSBindings/Mac/Clock SignalTests/8088Tests.mm index b4086eff4..e63655c66 100644 --- a/OSBindings/Mac/Clock SignalTests/8088Tests.mm +++ b/OSBindings/Mac/Clock SignalTests/8088Tests.mm @@ -401,13 +401,19 @@ struct FailedExecution { @"C4.json.gz", // LES @"8D.json.gz", // LEA - // TODO: MOVS, SCAS, STOS + // TODO: SCAS, INS, OUTS */ + // MOVS + @"A4.json.gz", @"A5.json.gz", + // CMPS @"A6.json.gz", @"A7.json.gz", // LODS @"AC.json.gz", @"AD.json.gz", + + // STOS + @"AA.json.gz", @"AB.json.gz", /* @"E0.json.gz", // LOOPNE @"E1.json.gz", // LOOPE From 4efc181f07f99210ca085988ca862ee008d683f8 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 20 Oct 2023 21:49:34 -0400 Subject: [PATCH 103/128] Fix memory handler, STOS. --- .../x86/Implementation/PerformImplementation.hpp | 6 +----- OSBindings/Mac/Clock SignalTests/8088Tests.mm | 4 ++-- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/InstructionSets/x86/Implementation/PerformImplementation.hpp b/InstructionSets/x86/Implementation/PerformImplementation.hpp index 57343b294..75342dcc9 100644 --- a/InstructionSets/x86/Implementation/PerformImplementation.hpp +++ b/InstructionSets/x86/Implementation/PerformImplementation.hpp @@ -1440,11 +1440,7 @@ void stos(const InstructionT &instruction, AddressT &eCX, AddressT &eDI, IntT &e return; } - Source destination_segment = instruction.segment_override(); - if(destination_segment == Source::None) destination_segment = Source::DS; - - memory.template access(destination_segment, eDI) = eAX; - + memory.template access(Source::ES, eDI) = eAX; eDI += status.direction() * sizeof(IntT); repeat(instruction, eCX, flow_controller); diff --git a/OSBindings/Mac/Clock SignalTests/8088Tests.mm b/OSBindings/Mac/Clock SignalTests/8088Tests.mm index e63655c66..7176b479b 100644 --- a/OSBindings/Mac/Clock SignalTests/8088Tests.mm +++ b/OSBindings/Mac/Clock SignalTests/8088Tests.mm @@ -156,7 +156,7 @@ struct Memory { // to the start. So the 16-bit value will need to be a local cache. if(address == 0xffff) { write_back_address_ = (segment_base(segment) + address) & 0xf'ffff; - write_back_value_ = memory[write_back_address_] | (memory[write_back_address_ - 65535] << 8); + write_back_value_ = memory[write_back_address_] | (memory[(write_back_address_ - 65535) & 0xf'ffff] << 8); return write_back_value_; } } @@ -168,7 +168,7 @@ struct Memory { if constexpr (std::is_same_v) { if(write_back_address_) { memory[write_back_address_] = write_back_value_ & 0xff; - memory[write_back_address_ - 65535] = write_back_value_ >> 8; + memory[(write_back_address_ - 65535) & 0xf'ffff] = write_back_value_ >> 8; write_back_address_ = 0; } } From c2ebbe5ad916687bc989645a55af798f677617c2 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 20 Oct 2023 21:54:30 -0400 Subject: [PATCH 104/128] Implement STOS with one failure. --- .../Implementation/PerformImplementation.hpp | 17 +++++++++++++++++ OSBindings/Mac/Clock SignalTests/8088Tests.mm | 3 +++ 2 files changed, 20 insertions(+) diff --git a/InstructionSets/x86/Implementation/PerformImplementation.hpp b/InstructionSets/x86/Implementation/PerformImplementation.hpp index 75342dcc9..af4602a33 100644 --- a/InstructionSets/x86/Implementation/PerformImplementation.hpp +++ b/InstructionSets/x86/Implementation/PerformImplementation.hpp @@ -1402,6 +1402,20 @@ void cmps(const InstructionT &instruction, AddressT &eCX, AddressT &eSI, Address repeat_ene(instruction, status, eCX, flow_controller); } +template +void scas(const InstructionT &instruction, AddressT &eCX, AddressT &eDI, IntT &eAX, MemoryT &memory, Status &status, FlowControllerT &flow_controller) { + if(repetition_over(instruction, eCX)) { + return; + } + + const IntT rhs = memory.template access(Source::ES, eDI); + eDI += status.direction() * sizeof(IntT); + + Primitive::sub(eAX, rhs, status); + + repeat_ene(instruction, status, eCX, flow_controller); +} + template void lods(const InstructionT &instruction, AddressT &eCX, AddressT &eSI, IntT &eAX, MemoryT &memory, Status &status, FlowControllerT &flow_controller) { if(repetition_over(instruction, eCX)) { @@ -1698,6 +1712,9 @@ template < case Operation::STOS: Primitive::stos(instruction, eCX(), eDI(), pair_low(), memory, status, flow_controller); break; + case Operation::SCAS: + Primitive::scas(instruction, eCX(), eDI(), pair_low(), memory, status, flow_controller); + break; } // Write to memory if required to complete this operation. diff --git a/OSBindings/Mac/Clock SignalTests/8088Tests.mm b/OSBindings/Mac/Clock SignalTests/8088Tests.mm index 7176b479b..36c756e38 100644 --- a/OSBindings/Mac/Clock SignalTests/8088Tests.mm +++ b/OSBindings/Mac/Clock SignalTests/8088Tests.mm @@ -414,6 +414,9 @@ struct FailedExecution { // STOS @"AA.json.gz", @"AB.json.gz", + + // SCAS + @"AE.json.gz", @"AF.json.gz", /* @"E0.json.gz", // LOOPNE @"E1.json.gz", // LOOPE From bf6fd8e5e44504b4a7277cea4653a731cda86dfa Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 20 Oct 2023 21:57:03 -0400 Subject: [PATCH 105/128] Shuffle down TODO. --- 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 36c756e38..2fd82d375 100644 --- a/OSBindings/Mac/Clock SignalTests/8088Tests.mm +++ b/OSBindings/Mac/Clock SignalTests/8088Tests.mm @@ -401,8 +401,6 @@ struct FailedExecution { @"C4.json.gz", // LES @"8D.json.gz", // LEA - // TODO: SCAS, INS, OUTS -*/ // MOVS @"A4.json.gz", @"A5.json.gz", @@ -415,6 +413,8 @@ struct FailedExecution { // STOS @"AA.json.gz", @"AB.json.gz", + // TODO: INS, OUTS +*/ // SCAS @"AE.json.gz", @"AF.json.gz", /* From aade91f0431010c086173d56c6f331e943cf0b25 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 21 Oct 2023 22:37:25 -0400 Subject: [PATCH 106/128] Implement IN, OUT. --- .../Implementation/PerformImplementation.hpp | 29 ++++++++++++++++++- OSBindings/Mac/Clock SignalTests/8088Tests.mm | 18 ++++++++---- 2 files changed, 41 insertions(+), 6 deletions(-) diff --git a/InstructionSets/x86/Implementation/PerformImplementation.hpp b/InstructionSets/x86/Implementation/PerformImplementation.hpp index af4602a33..aacad8c6d 100644 --- a/InstructionSets/x86/Implementation/PerformImplementation.hpp +++ b/InstructionSets/x86/Implementation/PerformImplementation.hpp @@ -1460,6 +1460,16 @@ void stos(const InstructionT &instruction, AddressT &eCX, AddressT &eDI, IntT &e repeat(instruction, eCX, flow_controller); } +template +void out(uint16_t port, IntT value, IOT &io) { + io.template out(port, value); +} + +template +void in(uint16_t port, IntT &value, IOT &io) { + value = io.template in(port); +} + } template < @@ -1476,7 +1486,7 @@ template < FlowControllerT &flow_controller, RegistersT ®isters, MemoryT &memory, - [[maybe_unused]] IOT &io + IOT &io ) { using IntT = typename DataSizeType::type; using AddressT = uint16_t; // TODO. @@ -1693,6 +1703,23 @@ template < } return; + case Operation::OUT: { + uint16_t port; + switch(instruction.destination().source()) { + case Source::DirectAddress: port = instruction.operand(); break; + default: port = registers.dx(); break; + } + Primitive::out(port, pair_low(), io); + } return; + case Operation::IN: { + uint16_t port; + switch(instruction.source().source()) { + case Source::DirectAddress: port = instruction.operand(); break; + default: port = registers.dx(); break; + } + Primitive::in(port, pair_low(), io); + } return; + case Operation::XLAT: Primitive::xlat(instruction, memory, registers); return; case Operation::POP: source() = Primitive::pop(memory, registers); break; diff --git a/OSBindings/Mac/Clock SignalTests/8088Tests.mm b/OSBindings/Mac/Clock SignalTests/8088Tests.mm index 2fd82d375..d63b25555 100644 --- a/OSBindings/Mac/Clock SignalTests/8088Tests.mm +++ b/OSBindings/Mac/Clock SignalTests/8088Tests.mm @@ -179,6 +179,8 @@ struct Memory { uint16_t write_back_value_; }; struct IO { + template void out([[maybe_unused]] uint16_t port, [[maybe_unused]] IntT value) {} + template IntT in([[maybe_unused]] uint16_t port) { return IntT(~0); } }; class FlowController { public: @@ -352,9 +354,15 @@ struct FailedExecution { @"4C.json.gz", @"4D.json.gz", @"4E.json.gz", @"4F.json.gz", @"FE.1.json.gz", @"FF.1.json.gz", +*/ + // OUT + @"E6.json.gz", @"E7.json.gz", + @"EE.json.gz", @"EF.json.gz", - // TODO: IN, OUT - + // IN + @"E4.json.gz", @"E5.json.gz", + @"EC.json.gz", @"ED.json.gz", +/* @"70.json.gz", // JO @"71.json.gz", // JNO @"72.json.gz", // JB @@ -413,11 +421,11 @@ struct FailedExecution { // STOS @"AA.json.gz", @"AB.json.gz", - // TODO: INS, OUTS -*/ // SCAS @"AE.json.gz", @"AF.json.gz", -/* + + // TODO: INS, OUTS + @"E0.json.gz", // LOOPNE @"E1.json.gz", // LOOPE @"E2.json.gz", // LOOP From e3cdf113d1b240ddceb95c5fe4e3bac94e138d2b Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 21 Oct 2023 22:52:50 -0400 Subject: [PATCH 107/128] Implement INS, OUTS. --- .../Implementation/PerformImplementation.hpp | 58 ++++++++++++++----- OSBindings/Mac/Clock SignalTests/8088Tests.mm | 12 ++-- 2 files changed, 50 insertions(+), 20 deletions(-) diff --git a/InstructionSets/x86/Implementation/PerformImplementation.hpp b/InstructionSets/x86/Implementation/PerformImplementation.hpp index aacad8c6d..cb214e35f 100644 --- a/InstructionSets/x86/Implementation/PerformImplementation.hpp +++ b/InstructionSets/x86/Implementation/PerformImplementation.hpp @@ -1460,6 +1460,32 @@ void stos(const InstructionT &instruction, AddressT &eCX, AddressT &eDI, IntT &e repeat(instruction, eCX, flow_controller); } +template +void outs(const InstructionT &instruction, AddressT &eCX, uint16_t port, AddressT &eSI, MemoryT &memory, IOT &io, Status &status, FlowControllerT &flow_controller) { + if(repetition_over(instruction, eCX)) { + return; + } + + Source source_segment = instruction.segment_override(); + if(source_segment == Source::None) source_segment = Source::DS; + io.template out(port, memory.template access(source_segment, eSI)); + eSI += status.direction() * sizeof(IntT); + + repeat(instruction, eCX, flow_controller); +} + +template +void ins(const InstructionT &instruction, AddressT &eCX, uint16_t port, AddressT &eDI, MemoryT &memory, IOT &io, Status &status, FlowControllerT &flow_controller) { + if(repetition_over(instruction, eCX)) { + return; + } + + memory.template access(Source::ES, eDI) = io.template in(port); + eDI += status.direction() * sizeof(IntT); + + repeat(instruction, eCX, flow_controller); +} + template void out(uint16_t port, IntT value, IOT &io) { io.template out(port, value); @@ -1571,6 +1597,14 @@ template < } }; + // Gets the port for an IN or OUT; these are always 16-bit. + const auto port = [&](Source source) -> uint16_t { + switch(source) { + case Source::DirectAddress: return instruction.operand(); + default: return registers.dx(); + } + }; + // Guide to the below: // // * use hard-coded register names where appropriate; @@ -1703,22 +1737,8 @@ template < } return; - case Operation::OUT: { - uint16_t port; - switch(instruction.destination().source()) { - case Source::DirectAddress: port = instruction.operand(); break; - default: port = registers.dx(); break; - } - Primitive::out(port, pair_low(), io); - } return; - case Operation::IN: { - uint16_t port; - switch(instruction.source().source()) { - case Source::DirectAddress: port = instruction.operand(); break; - default: port = registers.dx(); break; - } - Primitive::in(port, pair_low(), io); - } return; + case Operation::OUT: Primitive::out(port(instruction.destination().source()), pair_low(), io); return; + case Operation::IN: Primitive::in(port(instruction.source().source()), pair_low(), io); return; case Operation::XLAT: Primitive::xlat(instruction, memory, registers); return; @@ -1742,6 +1762,12 @@ template < case Operation::SCAS: Primitive::scas(instruction, eCX(), eDI(), pair_low(), memory, status, flow_controller); break; + case Operation::OUTS: + Primitive::outs(instruction, eCX(), registers.dx(), eSI(), memory, io, status, flow_controller); + break; + case Operation::INS: + Primitive::outs(instruction, eCX(), registers.dx(), eDI(), memory, io, status, flow_controller); + break; } // Write to memory if required to complete this operation. diff --git a/OSBindings/Mac/Clock SignalTests/8088Tests.mm b/OSBindings/Mac/Clock SignalTests/8088Tests.mm index d63b25555..f26210526 100644 --- a/OSBindings/Mac/Clock SignalTests/8088Tests.mm +++ b/OSBindings/Mac/Clock SignalTests/8088Tests.mm @@ -354,7 +354,7 @@ struct FailedExecution { @"4C.json.gz", @"4D.json.gz", @"4E.json.gz", @"4F.json.gz", @"FE.1.json.gz", @"FF.1.json.gz", -*/ + // OUT @"E6.json.gz", @"E7.json.gz", @"EE.json.gz", @"EF.json.gz", @@ -362,7 +362,7 @@ struct FailedExecution { // IN @"E4.json.gz", @"E5.json.gz", @"EC.json.gz", @"ED.json.gz", -/* + @"70.json.gz", // JO @"71.json.gz", // JNO @"72.json.gz", // JB @@ -423,9 +423,13 @@ struct FailedExecution { // SCAS @"AE.json.gz", @"AF.json.gz", +*/ + // OUTS + @"6E.json.gz", @"6F.json.gz", - // TODO: INS, OUTS - + // INS + @"6C.json.gz", @"6D.json.gz", +/* @"E0.json.gz", // LOOPNE @"E1.json.gz", // LOOPE @"E2.json.gz", // LOOP From 599c123b36122b4f1efe7ba4322405685c19e2dd Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 21 Oct 2023 22:55:10 -0400 Subject: [PATCH 108/128] Reenable all tests. --- OSBindings/Mac/Clock SignalTests/8088Tests.mm | 240 ------------------ 1 file changed, 240 deletions(-) diff --git a/OSBindings/Mac/Clock SignalTests/8088Tests.mm b/OSBindings/Mac/Clock SignalTests/8088Tests.mm index f26210526..aad5bb1ae 100644 --- a/OSBindings/Mac/Clock SignalTests/8088Tests.mm +++ b/OSBindings/Mac/Clock SignalTests/8088Tests.mm @@ -299,246 +299,6 @@ struct FailedExecution { - (NSArray *)testFiles { NSString *path = [NSString stringWithUTF8String:TestSuiteHome]; NSSet *allowList = [NSSet setWithArray:@[ -/* @"37.json.gz", // AAA - @"3F.json.gz", // AAS - @"D4.json.gz", // AAM - @"D5.json.gz", // AAD - @"27.json.gz", // DAA - @"2F.json.gz", // DAS - - @"98.json.gz", // CBW - @"99.json.gz", // CWD - - // ESC - @"D8.json.gz", @"D9.json.gz", @"DA.json.gz", @"DB.json.gz", - @"DC.json.gz", @"DD.json.gz", @"DE.json.gz", @"DE.json.gz", - - // Untested: HLT, WAIT - - // ADC - @"10.json.gz", @"11.json.gz", @"12.json.gz", @"13.json.gz", @"14.json.gz", @"15.json.gz", - @"80.2.json.gz", @"81.2.json.gz", @"83.2.json.gz", - - // ADD - @"00.json.gz", @"01.json.gz", @"02.json.gz", @"03.json.gz", @"04.json.gz", @"05.json.gz", - @"80.0.json.gz", @"81.0.json.gz", @"83.0.json.gz", - - // SBB - @"18.json.gz", @"19.json.gz", @"1A.json.gz", @"1B.json.gz", @"1C.json.gz", @"1D.json.gz", - @"80.3.json.gz", @"81.3.json.gz", @"83.3.json.gz", - - // SUB - @"28.json.gz", @"29.json.gz", @"2A.json.gz", @"2B.json.gz", @"2C.json.gz", @"2D.json.gz", - @"80.5.json.gz", @"81.5.json.gz", @"83.5.json.gz", - - // MUL - @"F6.4.json.gz", @"F7.4.json.gz", - - // IMUL_1 - @"F6.5.json.gz", @"F7.5.json.gz", - - // DIV - @"F6.6.json.gz", @"F7.6.json.gz", - - // IDIV - @"F6.7.json.gz", @"F7.7.json.gz", - - // INC - @"40.json.gz", @"41.json.gz", @"42.json.gz", @"43.json.gz", - @"44.json.gz", @"45.json.gz", @"46.json.gz", @"47.json.gz", - @"FE.0.json.gz", - @"FF.0.json.gz", - - // DEC - @"48.json.gz", @"49.json.gz", @"4A.json.gz", @"4B.json.gz", - @"4C.json.gz", @"4D.json.gz", @"4E.json.gz", @"4F.json.gz", - @"FE.1.json.gz", - @"FF.1.json.gz", - - // OUT - @"E6.json.gz", @"E7.json.gz", - @"EE.json.gz", @"EF.json.gz", - - // IN - @"E4.json.gz", @"E5.json.gz", - @"EC.json.gz", @"ED.json.gz", - - @"70.json.gz", // JO - @"71.json.gz", // JNO - @"72.json.gz", // JB - @"73.json.gz", // JNB - @"74.json.gz", // JZ - @"75.json.gz", // JNZ - @"76.json.gz", // JBE - @"77.json.gz", // JNBE - @"78.json.gz", // JS - @"79.json.gz", // JNS - @"7A.json.gz", // JP - @"7B.json.gz", // JNP - @"7C.json.gz", // JL - @"7D.json.gz", // JNL - @"7E.json.gz", // JLE - @"7F.json.gz", // JNLE - - // CALL - @"E8.json.gz", @"FF.2.json.gz", - @"9A.json.gz", @"FF.3.json.gz", - - // IRET - @"CF.json.gz", - - @"C3.json.gz", @"C2.json.gz", // near RET - @"CB.json.gz", @"CA.json.gz", // far RET - - @"EB.json.gz", @"E9.json.gz", // relative JMP - @"EA.josn.gz", @"FF.5.json.gz", // far JMP - - @"FF.4.json.gz", // absolute JMP - @"E3.json.gz", // JCXZ - - // INTO - @"CE.json.gz", - - // INT, INT3 - @"CC.json.gz", @"CD.json.gz", - - @"9E.json.gz", // SAHF - @"9F.json.gz", // LAHF - - @"C5.json.gz", // LDS - @"C4.json.gz", // LES - @"8D.json.gz", // LEA - - // MOVS - @"A4.json.gz", @"A5.json.gz", - - // CMPS - @"A6.json.gz", @"A7.json.gz", - - // LODS - @"AC.json.gz", @"AD.json.gz", - - // STOS - @"AA.json.gz", @"AB.json.gz", - - // SCAS - @"AE.json.gz", @"AF.json.gz", -*/ - // OUTS - @"6E.json.gz", @"6F.json.gz", - - // INS - @"6C.json.gz", @"6D.json.gz", -/* - @"E0.json.gz", // LOOPNE - @"E1.json.gz", // LOOPE - @"E2.json.gz", // LOOP - - // MOV - @"88.json.gz", @"89.json.gz", @"8A.json.gz", @"8B.json.gz", - @"8C.json.gz", @"8E.json.gz", - @"A0.json.gz", @"A1.json.gz", @"A2.json.gz", @"A3.json.gz", - @"B0.json.gz", @"B1.json.gz", @"B2.json.gz", @"B3.json.gz", - @"B4.json.gz", @"B5.json.gz", @"B6.json.gz", @"B7.json.gz", - @"B8.json.gz", @"B9.json.gz", @"BA.json.gz", @"BB.json.gz", - @"BC.json.gz", @"BD.json.gz", @"BE.json.gz", @"BF.json.gz", - @"C6.json.gz", @"C7.json.gz", - - // AND - @"20.json.gz", @"21.json.gz", @"22.json.gz", @"23.json.gz", @"24.json.gz", @"25.json.gz", - @"80.4.json.gz", @"81.4.json.gz", @"83.4.json.gz", - - // OR - @"08.json.gz", @"09.json.gz", @"0A.json.gz", @"0B.json.gz", @"0C.json.gz", @"0D.json.gz", - @"80.1.json.gz", @"81.1.json.gz", @"83.1.json.gz", - - // XOR - @"30.json.gz", @"31.json.gz", @"32.json.gz", @"33.json.gz", @"34.json.gz", @"35.json.gz", - @"80.6.json.gz", @"81.6.json.gz", @"83.6.json.gz", - - // NEG - @"F6.3.json.gz", @"F7.3.json.gz", - - // NOT - @"F6.2.json.gz", @"F7.2.json.gz", - - // NOP - @"90.json.gz", - - // POP - @"07.json.gz", @"0F.json.gz", @"17.json.gz", @"1F.json.gz", - @"58.json.gz", @"59.json.gz", @"5A.json.gz", @"5B.json.gz", - @"5C.json.gz", @"5D.json.gz", @"5E.json.gz", @"5F.json.gz", - @"8F.json.gz", - - // PUSH - @"06.json.gz", @"0E.json.gz", @"16.json.gz", @"1E.json.gz", - @"50.json.gz", @"51.json.gz", @"52.json.gz", @"53.json.gz", - @"54.json.gz", @"55.json.gz", @"56.json.gz", @"57.json.gz", - @"FF.6.json.gz", - - // POPF - @"9D.json.gz", - - // PUSHF - @"9C.json.gz", - - // RCL - @"D0.2.json.gz", @"D2.2.json.gz", - @"D1.2.json.gz", @"D3.2.json.gz", - - // RCR - @"D0.3.json.gz", @"D2.3.json.gz", - @"D1.3.json.gz", @"D3.3.json.gz", - - // ROL - @"D0.0.json.gz", @"D2.0.json.gz", - @"D1.0.json.gz", @"D3.0.json.gz", - - // ROR - @"D0.1.json.gz", @"D2.1.json.gz", - @"D1.1.json.gz", @"D3.1.json.gz", - - // SAL - @"D0.4.json.gz", @"D2.4.json.gz", - @"D1.4.json.gz", @"D3.4.json.gz", - - // SAR - @"D0.7.json.gz", @"D2.7.json.gz", - @"D1.7.json.gz", @"D3.7.json.gz", - - // SHR - @"D0.5.json.gz", @"D2.5.json.gz", - @"D1.5.json.gz", @"D3.5.json.gz", - - @"F8.json.gz", // CLC - @"FC.json.gz", // CLD - @"FA.json.gz", // CLI - @"F9.json.gz", // STC - @"FD.json.gz", // STD - @"FB.json.gz", // STI - @"F5.json.gz", // CMC - - // CMP - @"38.json.gz", @"39.json.gz", @"3A.json.gz", - @"3B.json.gz", @"3C.json.gz", @"3D.json.gz", - @"80.7.json.gz", @"81.7.json.gz", @"83.7.json.gz", - - // TEST - @"84.json.gz", @"85.json.gz", - @"A8.json.gz", @"A9.json.gz", - @"F6.0.json.gz", @"F7.0.json.gz", - - // XCHG - @"86.json.gz", @"87.json.gz", - @"91.json.gz", @"92.json.gz", @"93.json.gz", @"94.json.gz", - @"95.json.gz", @"96.json.gz", @"97.json.gz", - - @"D7.json.gz", // XLAT - @"D6.json.gz", // SALC - @"D0.6.json.gz", @"D1.6.json.gz", // SETMO - @"D2.6.json.gz", @"D3.6.json.gz", // SETMOC -*/ ]]; NSSet *ignoreList = nil; From 817a30332c4a97d84bc989af169a74400f4f3252 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 22 Oct 2023 22:15:27 -0400 Subject: [PATCH 109/128] Take a swing at LEA r16, r16. --- .../Implementation/PerformImplementation.hpp | 93 ++++++++++++------- OSBindings/Mac/Clock SignalTests/8088Tests.mm | 6 +- 2 files changed, 64 insertions(+), 35 deletions(-) diff --git a/InstructionSets/x86/Implementation/PerformImplementation.hpp b/InstructionSets/x86/Implementation/PerformImplementation.hpp index cb214e35f..b0fdfd17d 100644 --- a/InstructionSets/x86/Implementation/PerformImplementation.hpp +++ b/InstructionSets/x86/Implementation/PerformImplementation.hpp @@ -55,36 +55,8 @@ uint32_t address( return address + *resolve(instruction, pointer.base(), pointer, registers, memory); } -template -uint32_t address( - InstructionT &instruction, - DataPointer pointer, - RegistersT ®isters, - MemoryT &memory -) { - switch(pointer.source()) { - default: return 0; - case Source::Indirect: return address(instruction, pointer, registers, memory); - case Source::IndirectNoBase: return address(instruction, pointer, registers, memory); - case Source::DirectAddress: return address(instruction, pointer, registers, memory); - } -} - -template -IntT *resolve( - InstructionT &instruction, - Source source, - DataPointer pointer, - RegistersT ®isters, - MemoryT &memory, - IntT *none, - IntT *immediate -) { - // Rules: - // - // * if this is a memory access, set target_address and break; - // * otherwise return the appropriate value. - uint32_t target_address; +template +IntT *register_(RegistersT ®isters) { switch(source) { case Source::eAX: // Slightly contorted if chain here and below: @@ -131,10 +103,63 @@ IntT *resolve( else if constexpr (std::is_same_v) { return ®isters.bh(); } else { return nullptr; } - 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; + default: return nullptr; + } +} + +template +uint32_t address( + InstructionT &instruction, + DataPointer pointer, + RegistersT ®isters, + MemoryT &memory +) { + switch(pointer.source()) { + default: return 0; + case Source::eAX: return *register_(registers); + case Source::eCX: return *register_(registers); + case Source::eDX: return *register_(registers); + case Source::eBX: return *register_(registers); + case Source::eSPorAH: return *register_(registers); + case Source::eBPorCH: return *register_(registers); + case Source::eSIorDH: return *register_(registers); + case Source::eDIorBH: return *register_(registers); + case Source::Indirect: return address(instruction, pointer, registers, memory); + case Source::IndirectNoBase: return address(instruction, pointer, registers, memory); + case Source::DirectAddress: return address(instruction, pointer, registers, memory); + } +} + +template +IntT *resolve( + InstructionT &instruction, + Source source, + DataPointer pointer, + RegistersT ®isters, + MemoryT &memory, + IntT *none, + IntT *immediate +) { + // Rules: + // + // * if this is a memory access, set target_address and break; + // * otherwise return the appropriate value. + uint32_t target_address; + switch(source) { + case Source::eAX: return register_(registers); + case Source::eCX: return register_(registers); + case Source::eDX: return register_(registers); + case Source::eBX: return register_(registers); + case Source::eSPorAH: return register_(registers); + case Source::eBPorCH: return register_(registers); + case Source::eSIorDH: return register_(registers); + case Source::eDIorBH: return register_(registers); + + // Segment registers are always 16-bit. + case Source::ES: if constexpr (std::is_same_v) return ®isters.es(); else return nullptr; + case Source::CS: if constexpr (std::is_same_v) return ®isters.cs(); else return nullptr; + case Source::SS: if constexpr (std::is_same_v) return ®isters.ss(); else return nullptr; + case Source::DS: if constexpr (std::is_same_v) return ®isters.ds(); else return nullptr; // 16-bit models don't have FS and GS. case Source::FS: if constexpr (is_32bit(model) && std::is_same_v) return ®isters.fs(); else return nullptr; diff --git a/OSBindings/Mac/Clock SignalTests/8088Tests.mm b/OSBindings/Mac/Clock SignalTests/8088Tests.mm index aad5bb1ae..14562acf8 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 { @@ -497,6 +497,10 @@ struct FailedExecution { execution_support.status = initial_status; execution_support.registers = initial_registers; + if(decoded.second.operation != InstructionSet::x86::Operation::LEA) { + return; + } + // Execute instruction. // // TODO: enquire of the actual mechanism of repetition; if it were stateful as below then From 569cf8bf34a84a9939cc4f452c7d14f68f776228 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 23 Oct 2023 10:02:13 -0400 Subject: [PATCH 110/128] Focus on remaining files with issues. --- OSBindings/Mac/Clock SignalTests/8088Tests.mm | 27 ++++++++++++------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/OSBindings/Mac/Clock SignalTests/8088Tests.mm b/OSBindings/Mac/Clock SignalTests/8088Tests.mm index 14562acf8..4038b100d 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 { @@ -143,7 +143,7 @@ struct Memory { // to a selector, they're just at an absolute location. template IntT &access(uint32_t address, Tag tag) { if(tags.find(address) == tags.end()) { - printf("Access to unexpected RAM address"); +// printf("Access to unexpected RAM address"); } tags[address] = tag; return *reinterpret_cast(&memory[address]); @@ -299,6 +299,13 @@ struct FailedExecution { - (NSArray *)testFiles { NSString *path = [NSString stringWithUTF8String:TestSuiteHome]; NSSet *allowList = [NSSet setWithArray:@[ + @"27.json.gz", + @"2F.json.gz", + @"AB.json.gz", + @"D4.json.gz", + @"EA.json.gz", + @"F6.7.json.gz", + @"F7.7.json.gz", ]]; NSSet *ignoreList = nil; @@ -497,10 +504,6 @@ struct FailedExecution { execution_support.status = initial_status; execution_support.registers = initial_registers; - if(decoded.second.operation != InstructionSet::x86::Operation::LEA) { - return; - } - // Execute instruction. // // TODO: enquire of the actual mechanism of repetition; if it were stateful as below then @@ -619,8 +622,11 @@ struct FailedExecution { - (void)testExecution { NSDictionary *metadata = [self metadata]; + NSMutableArray *failures = [[NSMutableArray alloc] init]; for(NSString *file in [self testFiles]) @autoreleasepool { + const auto failures_before = execution_failures.size(); + // Determine the metadata key. NSString *const name = [file lastPathComponent]; NSRange first_dot = [name rangeOfString:@"."]; @@ -634,12 +640,13 @@ struct FailedExecution { int index = 0; for(NSDictionary *test in [self testsInFile:file]) { - if(index == 10) { - printf(""); - } [self applyExecutionTest:test metadata:test_metadata]; ++index; } + + if (execution_failures.size() != failures_before) { + [failures addObject:file]; + } } XCTAssertEqual(execution_failures.size(), 0); @@ -647,6 +654,8 @@ struct FailedExecution { for(const auto &failure: execution_failures) { NSLog(@"Failed %s — %s", failure.test_name.c_str(), failure.reason.c_str()); } + + NSLog(@"Files with failures were: %@", failures); } @end From 82c66e7433739510c5d36cfe2229d81e1e4d9e14 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 23 Oct 2023 10:07:19 -0400 Subject: [PATCH 111/128] Fix far jump with immediate operand. 1655 failures remaining. --- .../x86/Implementation/PerformImplementation.hpp | 2 +- OSBindings/Mac/Clock SignalTests/8088Tests.mm | 13 ++++++------- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/InstructionSets/x86/Implementation/PerformImplementation.hpp b/InstructionSets/x86/Implementation/PerformImplementation.hpp index b0fdfd17d..839bc3386 100644 --- a/InstructionSets/x86/Implementation/PerformImplementation.hpp +++ b/InstructionSets/x86/Implementation/PerformImplementation.hpp @@ -878,7 +878,7 @@ void jump_far(InstructionT &instruction, const auto pointer = instruction.destination(); switch(pointer.template source()) { default: - case Source::Immediate: flow_controller.call(instruction.segment(), instruction.offset()); return; + case Source::Immediate: flow_controller.jump(instruction.segment(), instruction.offset()); return; case Source::Indirect: source_address = address(instruction, pointer, registers, memory); diff --git a/OSBindings/Mac/Clock SignalTests/8088Tests.mm b/OSBindings/Mac/Clock SignalTests/8088Tests.mm index 4038b100d..c1039bc25 100644 --- a/OSBindings/Mac/Clock SignalTests/8088Tests.mm +++ b/OSBindings/Mac/Clock SignalTests/8088Tests.mm @@ -299,13 +299,12 @@ struct FailedExecution { - (NSArray *)testFiles { NSString *path = [NSString stringWithUTF8String:TestSuiteHome]; NSSet *allowList = [NSSet setWithArray:@[ - @"27.json.gz", - @"2F.json.gz", - @"AB.json.gz", - @"D4.json.gz", - @"EA.json.gz", - @"F6.7.json.gz", - @"F7.7.json.gz", + @"27.json.gz", // DAA + @"2F.json.gz", // DAS + @"AB.json.gz", // STOS[w] + @"D4.json.gz", // AAM + @"F6.7.json.gz", // IDIV + @"F7.7.json.gz", // IDIV ]]; NSSet *ignoreList = nil; From 7a4d74b8e4c0fe975acddc41b0bd59b9c155060f Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 23 Oct 2023 10:07:57 -0400 Subject: [PATCH 112/128] Correct copy and paste error: 0x27 is DAA. --- InstructionSets/x86/Documentation/80386 opcode map.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/InstructionSets/x86/Documentation/80386 opcode map.html b/InstructionSets/x86/Documentation/80386 opcode map.html index 3bab74c42..09e6330e5 100644 --- a/InstructionSets/x86/Documentation/80386 opcode map.html +++ b/InstructionSets/x86/Documentation/80386 opcode map.html @@ -224,7 +224,7 @@ AND SEG =ES - POP ES + DAA SUB SEG =CS DAS From 49d87c9f2708a8d3dca11e83fd0ad47b09178fbc Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 23 Oct 2023 10:41:58 -0400 Subject: [PATCH 113/128] Fix 16-bit accesses that overlap memory's end. 1654 failures remaining. --- OSBindings/Mac/Clock SignalTests/8088Tests.mm | 41 ++++++++++++++----- 1 file changed, 30 insertions(+), 11 deletions(-) diff --git a/OSBindings/Mac/Clock SignalTests/8088Tests.mm b/OSBindings/Mac/Clock SignalTests/8088Tests.mm index c1039bc25..78bf0267c 100644 --- a/OSBindings/Mac/Clock SignalTests/8088Tests.mm +++ b/OSBindings/Mac/Clock SignalTests/8088Tests.mm @@ -142,8 +142,25 @@ struct Memory { // An additional entry point for the flow controller; on the original 8086 interrupt vectors aren't relative // to a selector, they're just at an absolute location. template IntT &access(uint32_t address, Tag tag) { + // Check for address wraparound + if(address >= 0x10'0001 - sizeof(IntT)) { + if constexpr (std::is_same_v) { + address &= 0xf'ffff; + } else { + if(address == 0xf'ffff) { + // This is a 16-bit access comprising the final byte in memory and the first. + write_back_address_[0] = address; + write_back_address_[1] = 0; + write_back_value_ = memory[write_back_address_[0]] | (memory[write_back_address_[1]] << 8); + return write_back_value_; + } else { + address &= 0xf'ffff; + } + } + } + if(tags.find(address) == tags.end()) { -// printf("Access to unexpected RAM address"); + printf("Access to unexpected RAM address"); } tags[address] = tag; return *reinterpret_cast(&memory[address]); @@ -155,8 +172,9 @@ struct Memory { // 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_ = (segment_base(segment) + address) & 0xf'ffff; - write_back_value_ = memory[write_back_address_] | (memory[(write_back_address_ - 65535) & 0xf'ffff] << 8); + 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_; } } @@ -166,16 +184,16 @@ struct Memory { template void write_back() { if constexpr (std::is_same_v) { - if(write_back_address_) { - memory[write_back_address_] = write_back_value_ & 0xff; - memory[(write_back_address_ - 65535) & 0xf'ffff] = write_back_value_ >> 8; - write_back_address_ = 0; + if(write_back_address_[0] != NoWriteBack) { + memory[write_back_address_[0]] = write_back_value_ & 0xff; + memory[write_back_address_[1]] = write_back_value_ >> 8; + write_back_address_[0] = 0; } } } - static constexpr uint32_t NoWriteBack = 0; // Zero can never be an address that triggers write back, conveniently. - uint32_t write_back_address_ = NoWriteBack; + 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 { @@ -301,7 +319,6 @@ struct FailedExecution { NSSet *allowList = [NSSet setWithArray:@[ @"27.json.gz", // DAA @"2F.json.gz", // DAS - @"AB.json.gz", // STOS[w] @"D4.json.gz", // AAM @"F6.7.json.gz", // IDIV @"F7.7.json.gz", // IDIV @@ -537,7 +554,9 @@ struct FailedExecution { } } - ramEqual &= (execution_support.memory.memory[address] & mask) == ([ram[1] intValue] & mask); + if((execution_support.memory.memory[address] & mask) != ([ram[1] intValue] & mask)) { + ramEqual = false; + } } [self populate:intended_registers status:intended_status value:final_state[@"regs"]]; From 20d7079006a28b390d9a4c95568c58e65f30d544 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 23 Oct 2023 16:37:27 -0400 Subject: [PATCH 114/128] Start adaptation to new test disassembly form. --- InstructionSets/x86/Instruction.cpp | 45 +++++------ InstructionSets/x86/Instruction.hpp | 12 ++- OSBindings/Mac/Clock SignalTests/8088Tests.mm | 79 ++++++++++++++++--- 3 files changed, 94 insertions(+), 42 deletions(-) diff --git a/InstructionSets/x86/Instruction.cpp b/InstructionSets/x86/Instruction.cpp index fd0a9b8d5..eebf4af33 100644 --- a/InstructionSets/x86/Instruction.cpp +++ b/InstructionSets/x86/Instruction.cpp @@ -8,6 +8,8 @@ #include "Instruction.hpp" +#include "../../Numeric/Carry.hpp" + #include #include #include @@ -335,33 +337,32 @@ std::string InstructionSet::x86::to_string( std::string operand; - auto append = [](std::stringstream &stream, auto value, int length, const char *prefix) { + auto append = [](std::stringstream &stream, auto value, int length) { switch(length) { case 0: if(!value) { - break; + return; } [[fallthrough]]; + case 2: - // If asked to pretend the offset was originally two digits then either of: an unsigned - // 8-bit value or a sign-extended 8-bit value as having been originally 8-bit. - // - // This kicks the issue of whether sign was extended appropriately to functionality tests. - if( - !(value & 0xff00) || - ((value & 0xff80) == 0xff80) || - ((value & 0xff80) == 0x0000) - ) { - stream << prefix << to_hex(value, 2); - break; - } - [[fallthrough]]; - default: - stream << prefix << to_hex(value, 4); - break; + value &= 0xff; + break; } + + stream << std::uppercase << std::hex << value << 'h'; }; + auto append_signed = [](std::stringstream &stream, auto value, int length) { + if(!value && !length) { + return; + } + + const bool is_negative = Numeric::top_bit() & value; + const uint64_t abs_value = std::abs(int16_t(value)); // TODO: don't assume 16-bit. + + stream << (is_negative ? '-' : '+') << std::uppercase << std::hex << abs_value << 'h'; + }; using Source = InstructionSet::x86::Source; const Source source = pointer.source(); switch(source) { @@ -370,7 +371,7 @@ std::string InstructionSet::x86::to_string( case Source::Immediate: { std::stringstream stream; - append(stream, instruction.operand(), immediate_length, ""); + append(stream, instruction.operand(), immediate_length); return stream.str(); } @@ -383,6 +384,7 @@ std::string InstructionSet::x86::to_string( stream << InstructionSet::x86::to_string(operation_size) << ' '; } + stream << '['; Source segment = instruction.segment_override(); if(segment == Source::None) { segment = pointer.default_segment(); @@ -392,7 +394,6 @@ std::string InstructionSet::x86::to_string( } stream << InstructionSet::x86::to_string(segment, InstructionSet::x86::DataSize::None) << ':'; - stream << '['; bool addOffset = false; switch(source) { default: break; @@ -408,11 +409,11 @@ std::string InstructionSet::x86::to_string( addOffset = true; break; case Source::DirectAddress: - stream << to_hex(instruction.offset(), 4); + stream << std::uppercase << std::hex << instruction.offset() << 'h'; break; } if(addOffset) { - append(stream, instruction.offset(), offset_length, "+"); + append_signed(stream, instruction.offset(), offset_length); } stream << ']'; return stream.str(); diff --git a/InstructionSets/x86/Instruction.hpp b/InstructionSets/x86/Instruction.hpp index 41417e30f..994674fd4 100644 --- a/InstructionSets/x86/Instruction.hpp +++ b/InstructionSets/x86/Instruction.hpp @@ -462,16 +462,14 @@ enum class Repetition: uint8_t { }; /// @returns @c true if @c operation supports repetition mode @c repetition; @c false otherwise. -constexpr bool supports(Operation operation, Repetition repetition) { +constexpr bool supports(Operation operation, [[maybe_unused]] Repetition repetition) { switch(operation) { default: return false; - case Operation::INS: - case Operation::OUTS: - return repetition == Repetition::RepE; - case Operation::Invalid: // Retain context here; it's used as an intermediate // state sometimes. + case Operation::INS: + case Operation::OUTS: case Operation::CMPS: case Operation::LODS: case Operation::MOVS: @@ -483,8 +481,8 @@ constexpr bool supports(Operation operation, Repetition repetition) { // IDIV — and possibly DIV — as a quirk, affecting the outcome (possibly negativing the result?). // So the test below should be a function of model, if I come to a conclusion about whether I'm // going for fidelity to the instruction set as generally implemented, or to Intel's specific implementation. - case Operation::IDIV: - return repetition == Repetition::RepNE; +// case Operation::IDIV: +// return repetition == Repetition::RepNE; } } diff --git a/OSBindings/Mac/Clock SignalTests/8088Tests.mm b/OSBindings/Mac/Clock SignalTests/8088Tests.mm index 78bf0267c..2c8e4faa2 100644 --- a/OSBindings/Mac/Clock SignalTests/8088Tests.mm +++ b/OSBindings/Mac/Clock SignalTests/8088Tests.mm @@ -317,11 +317,71 @@ struct FailedExecution { - (NSArray *)testFiles { NSString *path = [NSString stringWithUTF8String:TestSuiteHome]; NSSet *allowList = [NSSet setWithArray:@[ - @"27.json.gz", // DAA - @"2F.json.gz", // DAS - @"D4.json.gz", // AAM - @"F6.7.json.gz", // IDIV - @"F7.7.json.gz", // IDIV + // Current decoding failures: + @"60.json.gz", + @"61.json.gz", + @"62.json.gz", + @"63.json.gz", + @"64.json.gz", + @"65.json.gz", + @"66.json.gz", + @"67.json.gz", + @"68.json.gz", + @"69.json.gz", + @"6A.json.gz", + @"6B.json.gz", + @"6C.json.gz", + @"6D.json.gz", + @"6E.json.gz", + @"6F.json.gz", + @"70.json.gz", + @"71.json.gz", + @"72.json.gz", + @"73.json.gz", + @"74.json.gz", + @"75.json.gz", + @"76.json.gz", + @"77.json.gz", + @"78.json.gz", + @"79.json.gz", + @"7A.json.gz", + @"7B.json.gz", + @"7C.json.gz", + @"7D.json.gz", + @"7E.json.gz", + @"7F.json.gz", + @"9A.json.gz", + @"A4.json.gz", + @"A5.json.gz", + @"A6.json.gz", + @"A7.json.gz", + @"AA.json.gz", + @"AB.json.gz", + @"AC.json.gz", + @"AD.json.gz", + @"AE.json.gz", + @"AF.json.gz", + @"CC.json.gz", + @"E0.json.gz", + @"E1.json.gz", + @"E2.json.gz", + @"E3.json.gz", + @"E4.json.gz", + @"E5.json.gz", + @"E6.json.gz", + @"E7.json.gz", + @"E8.json.gz", + @"E9.json.gz", + @"EA.json.gz", + @"EB.json.gz", + + + // Current execution failures: +// @"27.json.gz", // DAA +// @"2F.json.gz", // DAS +// @"D4.json.gz", // AAM +// @"F6.7.json.gz", // IDIV +// @"F7.7.json.gz", // IDIV ]]; NSSet *ignoreList = nil; @@ -418,11 +478,7 @@ struct FailedExecution { // Attempt clerical reconciliation: // - // TEMPORARY HACK: the test set incorrectly states 'bp+si' whenever it means 'bp+di'. - // Though it also uses 'bp+si' correctly when it means 'bp+si'. Until fixed, take - // a pass on potential issues there. - // - // SEPARATELY: The test suite retains a distinction between SHL and SAL, which the decoder doesn't. So consider that + // The test suite retains a distinction between SHL and SAL, which the decoder doesn't. So consider that // a potential point of difference. // // Also, the decoder treats INT3 and INT 3 as the same thing. So allow for a meshing of those. @@ -430,9 +486,6 @@ struct FailedExecution { while(!isEqual && adjustment) { NSString *alteredName = [test[@"name"] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; - if(adjustment & 4) { - alteredName = [alteredName stringByReplacingOccurrencesOfString:@"bp+si" withString:@"bp+di"]; - } if(adjustment & 2) { alteredName = [alteredName stringByReplacingOccurrencesOfString:@"shl" withString:@"sal"]; } From 0e027445d447f9dc42ef69b3e26b5b6a5397bc1c Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 24 Oct 2023 11:01:38 -0400 Subject: [PATCH 115/128] Don't offer repne for reps; print far CALLs and JMPs as h-suffix rather than 0x prefix. --- InstructionSets/x86/Instruction.cpp | 22 +++++++++++++------ OSBindings/Mac/Clock SignalTests/8088Tests.mm | 7 ++---- 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/InstructionSets/x86/Instruction.cpp b/InstructionSets/x86/Instruction.cpp index eebf4af33..11fda40ac 100644 --- a/InstructionSets/x86/Instruction.cpp +++ b/InstructionSets/x86/Instruction.cpp @@ -437,19 +437,27 @@ std::string InstructionSet::x86::to_string( case Repetition::None: break; case Repetition::RepE: switch(instruction.operation) { - default: + case Operation::CMPS: + case Operation::SCAS: operation += "repe "; break; - case Operation::MOVS: - case Operation::STOS: - case Operation::LODS: + default: operation += "rep "; break; } break; case Repetition::RepNE: - operation += "repne "; + switch(instruction.operation) { + case Operation::CMPS: + case Operation::SCAS: + operation += "repne "; + break; + + default: + operation += "rep "; + break; + } break; } @@ -479,10 +487,10 @@ std::string InstructionSet::x86::to_string( case Operation::JMPfar: { switch(instruction.destination().source()) { case Source::Immediate: - operation += "far 0x"; operation += to_hex(instruction.segment(), 4, false); - operation += ":0x"; + operation += "h:"; operation += to_hex(instruction.offset(), 4, false); + operation += "h"; break; default: operation += to_string(instruction.destination(), instruction, offset_length, immediate_length); diff --git a/OSBindings/Mac/Clock SignalTests/8088Tests.mm b/OSBindings/Mac/Clock SignalTests/8088Tests.mm index 2c8e4faa2..a68ae569b 100644 --- a/OSBindings/Mac/Clock SignalTests/8088Tests.mm +++ b/OSBindings/Mac/Clock SignalTests/8088Tests.mm @@ -350,7 +350,6 @@ struct FailedExecution { @"7D.json.gz", @"7E.json.gz", @"7F.json.gz", - @"9A.json.gz", @"A4.json.gz", @"A5.json.gz", @"A6.json.gz", @@ -372,7 +371,6 @@ struct FailedExecution { @"E7.json.gz", @"E8.json.gz", @"E9.json.gz", - @"EA.json.gz", @"EB.json.gz", @@ -500,11 +498,10 @@ struct FailedExecution { if(assert) { XCTAssert( isEqual, - "%@ doesn't match %@ or similar, was %@ within %@", + "%@ doesn't match %@ or similar, was %@", test[@"name"], [decodings anyObject], - hex_instruction(), - file + hex_instruction() ); } From e36274e5c2c026e82fa2040acefae528c50dd647 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 24 Oct 2023 15:07:53 -0400 Subject: [PATCH 116/128] Add segment prefix for MOVS, LODS, etc. --- InstructionSets/x86/Instruction.cpp | 21 +++++++++++++++++++ OSBindings/Mac/Clock SignalTests/8088Tests.mm | 10 --------- 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/InstructionSets/x86/Instruction.cpp b/InstructionSets/x86/Instruction.cpp index 11fda40ac..9a039dbc3 100644 --- a/InstructionSets/x86/Instruction.cpp +++ b/InstructionSets/x86/Instruction.cpp @@ -432,6 +432,27 @@ std::string InstructionSet::x86::to_string( ) { std::string operation; + // Add segment override, if any, ahead of some operations that won't otherwise print it. + switch(instruction.operation) { + default: break; + + case Operation::CMPS: + case Operation::SCAS: + case Operation::STOS: + case Operation::LODS: + case Operation::MOVS: + switch(instruction.segment_override()) { + default: break; + case Source::ES: operation += "es "; break; + case Source::CS: operation += "cs "; break; + case Source::DS: operation += "ds "; break; + case Source::SS: operation += "ss "; break; + case Source::GS: operation += "gs "; break; + case Source::FS: operation += "fs "; break; + } + break; + } + // Add a repetition prefix; it'll be one of 'rep', 'repe' or 'repne'. switch(instruction.repetition()) { case Repetition::None: break; diff --git a/OSBindings/Mac/Clock SignalTests/8088Tests.mm b/OSBindings/Mac/Clock SignalTests/8088Tests.mm index a68ae569b..4e4fdf18a 100644 --- a/OSBindings/Mac/Clock SignalTests/8088Tests.mm +++ b/OSBindings/Mac/Clock SignalTests/8088Tests.mm @@ -350,16 +350,6 @@ struct FailedExecution { @"7D.json.gz", @"7E.json.gz", @"7F.json.gz", - @"A4.json.gz", - @"A5.json.gz", - @"A6.json.gz", - @"A7.json.gz", - @"AA.json.gz", - @"AB.json.gz", - @"AC.json.gz", - @"AD.json.gz", - @"AE.json.gz", - @"AF.json.gz", @"CC.json.gz", @"E0.json.gz", @"E1.json.gz", From 26c2a29b99eed1d3ce009319e08e0d170250e64b Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 24 Oct 2023 15:09:25 -0400 Subject: [PATCH 117/128] Fix int3 mapping. --- OSBindings/Mac/Clock SignalTests/8088Tests.mm | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/OSBindings/Mac/Clock SignalTests/8088Tests.mm b/OSBindings/Mac/Clock SignalTests/8088Tests.mm index 4e4fdf18a..77a7391ef 100644 --- a/OSBindings/Mac/Clock SignalTests/8088Tests.mm +++ b/OSBindings/Mac/Clock SignalTests/8088Tests.mm @@ -350,7 +350,6 @@ struct FailedExecution { @"7D.json.gz", @"7E.json.gz", @"7F.json.gz", - @"CC.json.gz", @"E0.json.gz", @"E1.json.gz", @"E2.json.gz", @@ -478,7 +477,7 @@ struct FailedExecution { alteredName = [alteredName stringByReplacingOccurrencesOfString:@"shl" withString:@"sal"]; } if(adjustment & 1) { - alteredName = [alteredName stringByReplacingOccurrencesOfString:@"int3" withString:@"int 03h"]; + alteredName = [alteredName stringByReplacingOccurrencesOfString:@"int3" withString:@"int 3h"]; } isEqual = compare_decoding(alteredName); From cc9e8117ab3b0521f6c335939c60d8760bc6ec72 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 24 Oct 2023 16:43:22 -0400 Subject: [PATCH 118/128] Add note. --- InstructionSets/x86/Instruction.hpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/InstructionSets/x86/Instruction.hpp b/InstructionSets/x86/Instruction.hpp index 994674fd4..9f8b4995b 100644 --- a/InstructionSets/x86/Instruction.hpp +++ b/InstructionSets/x86/Instruction.hpp @@ -702,6 +702,16 @@ template class Instruction { // [b4, b0]: dest. uint16_t source_data_dest_sib_ = 1 << 10; // So that ::Invalid doesn't seem to have a length extension. + // Note to future self: if source length continues to prove avoidable, reuse its four bits as: + // three bits: segment (as overridden, otherwise whichever operand has a segment, if either); + // one bit: an extra bit for Operation. + // + // Then what was the length extension will hold only a repetition, if any, and the lock bit. As luck would have + // it there are six valid segment registers so there is an available sentinel value to put into the segment + // field to indicate that there's an extension if necessary. A further three bits would need to be trimmed + // to do away with that extension entirely, but since lock is rarely used and repetitions apply only to a + // small number of operations I think it'd at least be a limited problem. + bool has_length_extension() const { return !((source_data_dest_sib_ >> 10) & 15); } From 239ce75db6cf4542d17a1a6fe840f4845bdaefde Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 24 Oct 2023 22:35:13 -0400 Subject: [PATCH 119/128] Fix IN and OUT conversion. --- InstructionSets/x86/Instruction.cpp | 11 +++++++++-- OSBindings/Mac/Clock SignalTests/8088Tests.mm | 4 ---- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/InstructionSets/x86/Instruction.cpp b/InstructionSets/x86/Instruction.cpp index 9a039dbc3..008dbfa08 100644 --- a/InstructionSets/x86/Instruction.cpp +++ b/InstructionSets/x86/Instruction.cpp @@ -323,6 +323,13 @@ std::string to_hex(int value, int digits, bool with_suffix = true) { return stream.str(); }; +template +std::string to_hex(IntT value) { + auto stream = std::stringstream(); + stream << std::uppercase << std::hex << +value << 'h'; + return stream.str(); +}; + } template @@ -532,7 +539,7 @@ std::string InstructionSet::x86::to_string( operation += ", "; switch(instruction.source().source()) { case Source::DirectAddress: - operation += to_hex(instruction.offset(), 2, true); + operation += to_hex(uint8_t(instruction.offset())); break; default: operation += to_string(instruction.source(), instruction, offset_length, immediate_length, InstructionSet::x86::DataSize::Word); @@ -543,7 +550,7 @@ std::string InstructionSet::x86::to_string( case Operation::OUT: switch(instruction.destination().source()) { case Source::DirectAddress: - operation += to_hex(instruction.offset(), 2, true); + operation += to_hex(uint8_t(instruction.offset())); break; default: operation += to_string(instruction.destination(), instruction, offset_length, immediate_length, InstructionSet::x86::DataSize::Word); diff --git a/OSBindings/Mac/Clock SignalTests/8088Tests.mm b/OSBindings/Mac/Clock SignalTests/8088Tests.mm index 77a7391ef..e27e8a67b 100644 --- a/OSBindings/Mac/Clock SignalTests/8088Tests.mm +++ b/OSBindings/Mac/Clock SignalTests/8088Tests.mm @@ -354,10 +354,6 @@ struct FailedExecution { @"E1.json.gz", @"E2.json.gz", @"E3.json.gz", - @"E4.json.gz", - @"E5.json.gz", - @"E6.json.gz", - @"E7.json.gz", @"E8.json.gz", @"E9.json.gz", @"EB.json.gz", From 6cecb84878ca0cb575ea16265f4998b5b6b4fd39 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 25 Oct 2023 09:09:51 -0400 Subject: [PATCH 120/128] Add #include. --- Numeric/Carry.hpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Numeric/Carry.hpp b/Numeric/Carry.hpp index bc565df62..1e28bfdfc 100644 --- a/Numeric/Carry.hpp +++ b/Numeric/Carry.hpp @@ -9,6 +9,8 @@ #ifndef Carry_hpp #define Carry_hpp +#include + namespace Numeric { /// @returns @c true if from @c bit there was: From 6dd5628dd6f6a908c144ff80e61bfe12b008ac97 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 25 Oct 2023 11:21:11 -0400 Subject: [PATCH 121/128] Provide full pair for string conversion. --- InstructionSets/x86/Instruction.cpp | 68 +++++++++---------- InstructionSets/x86/Instruction.hpp | 2 +- OSBindings/Mac/Clock SignalTests/8088Tests.mm | 14 ++-- 3 files changed, 42 insertions(+), 42 deletions(-) diff --git a/InstructionSets/x86/Instruction.cpp b/InstructionSets/x86/Instruction.cpp index 008dbfa08..da4a40107 100644 --- a/InstructionSets/x86/Instruction.cpp +++ b/InstructionSets/x86/Instruction.cpp @@ -432,7 +432,7 @@ std::string InstructionSet::x86::to_string( template std::string InstructionSet::x86::to_string( - Instruction instruction, + std::pair> instruction, Model model, int offset_length, int immediate_length @@ -440,7 +440,7 @@ std::string InstructionSet::x86::to_string( std::string operation; // Add segment override, if any, ahead of some operations that won't otherwise print it. - switch(instruction.operation) { + switch(instruction.second.operation) { default: break; case Operation::CMPS: @@ -448,7 +448,7 @@ std::string InstructionSet::x86::to_string( case Operation::STOS: case Operation::LODS: case Operation::MOVS: - switch(instruction.segment_override()) { + switch(instruction.second.segment_override()) { default: break; case Source::ES: operation += "es "; break; case Source::CS: operation += "cs "; break; @@ -461,10 +461,10 @@ std::string InstructionSet::x86::to_string( } // Add a repetition prefix; it'll be one of 'rep', 'repe' or 'repne'. - switch(instruction.repetition()) { + switch(instruction.second.repetition()) { case Repetition::None: break; case Repetition::RepE: - switch(instruction.operation) { + switch(instruction.second.operation) { case Operation::CMPS: case Operation::SCAS: operation += "repe "; @@ -476,7 +476,7 @@ std::string InstructionSet::x86::to_string( } break; case Repetition::RepNE: - switch(instruction.operation) { + switch(instruction.second.operation) { case Operation::CMPS: case Operation::SCAS: operation += "repne "; @@ -490,38 +490,38 @@ std::string InstructionSet::x86::to_string( } // Add operation itself. - operation += to_string(instruction.operation, instruction.operation_size(), model); + operation += to_string(instruction.second.operation, instruction.second.operation_size(), model); operation += " "; // Deal with a few special cases up front. - switch(instruction.operation) { + switch(instruction.second.operation) { default: { - const int operands = max_displayed_operands(instruction.operation); - const bool displacement = has_displacement(instruction.operation); - const bool print_first = operands > 1 && instruction.destination().source() != Source::None; + const int operands = max_displayed_operands(instruction.second.operation); + const bool displacement = has_displacement(instruction.second.operation); + const bool print_first = operands > 1 && instruction.second.destination().source() != Source::None; if(print_first) { - operation += to_string(instruction.destination(), instruction, offset_length, immediate_length); + operation += to_string(instruction.second.destination(), instruction.second, offset_length, immediate_length); } - if(operands > 0 && instruction.source().source() != Source::None) { + if(operands > 0 && instruction.second.source().source() != Source::None) { if(print_first) operation += ", "; - operation += to_string(instruction.source(), instruction, offset_length, immediate_length); + operation += to_string(instruction.second.source(), instruction.second, offset_length, immediate_length); } if(displacement) { - operation += to_hex(instruction.displacement(), offset_length); + operation += to_hex(instruction.second.displacement(), offset_length); } } break; case Operation::CALLfar: case Operation::JMPfar: { - switch(instruction.destination().source()) { + switch(instruction.second.destination().source()) { case Source::Immediate: - operation += to_hex(instruction.segment(), 4, false); + operation += to_hex(instruction.second.segment(), 4, false); operation += "h:"; - operation += to_hex(instruction.offset(), 4, false); + operation += to_hex(instruction.second.offset(), 4, false); operation += "h"; break; default: - operation += to_string(instruction.destination(), instruction, offset_length, immediate_length); + operation += to_string(instruction.second.destination(), instruction.second, offset_length, immediate_length); break; } } break; @@ -529,35 +529,35 @@ std::string InstructionSet::x86::to_string( case Operation::LDS: case Operation::LES: // The test set labels the pointer type as dword, which I guess is technically accurate. // A full 32 bits will be loaded from that address in 16-bit mode. - operation += to_string(instruction.destination(), instruction, offset_length, immediate_length); + operation += to_string(instruction.second.destination(), instruction.second, offset_length, immediate_length); operation += ", "; - operation += to_string(instruction.source(), instruction, offset_length, immediate_length, InstructionSet::x86::DataSize::DWord); + operation += to_string(instruction.second.source(), instruction.second, offset_length, immediate_length, InstructionSet::x86::DataSize::DWord); break; case Operation::IN: - operation += to_string(instruction.destination(), instruction, offset_length, immediate_length); + operation += to_string(instruction.second.destination(), instruction.second, offset_length, immediate_length); operation += ", "; - switch(instruction.source().source()) { + switch(instruction.second.source().source()) { case Source::DirectAddress: - operation += to_hex(uint8_t(instruction.offset())); + operation += to_hex(uint8_t(instruction.second.offset())); break; default: - operation += to_string(instruction.source(), instruction, offset_length, immediate_length, InstructionSet::x86::DataSize::Word); + operation += to_string(instruction.second.source(), instruction.second, offset_length, immediate_length, InstructionSet::x86::DataSize::Word); break; } break; case Operation::OUT: - switch(instruction.destination().source()) { + switch(instruction.second.destination().source()) { case Source::DirectAddress: - operation += to_hex(uint8_t(instruction.offset())); + operation += to_hex(uint8_t(instruction.second.offset())); break; default: - operation += to_string(instruction.destination(), instruction, offset_length, immediate_length, InstructionSet::x86::DataSize::Word); + operation += to_string(instruction.second.destination(), instruction.second, offset_length, immediate_length, InstructionSet::x86::DataSize::Word); break; } operation += ", "; - operation += to_string(instruction.source(), instruction, offset_length, immediate_length); + operation += to_string(instruction.second.source(), instruction.second, offset_length, immediate_length); break; // Rolls and shifts list eCX as a source on the understanding that everyone knows that rolls and shifts @@ -567,18 +567,18 @@ std::string InstructionSet::x86::to_string( case Operation::SAL: case Operation::SAR: case Operation::SHR: case Operation::SETMO: case Operation::SETMOC: - operation += to_string(instruction.destination(), instruction, offset_length, immediate_length); - switch(instruction.source().source()) { + operation += to_string(instruction.second.destination(), instruction.second, offset_length, immediate_length); + switch(instruction.second.source().source()) { case Source::None: break; case Source::eCX: operation += ", cl"; break; case Source::Immediate: // Providing an immediate operand of 1 is a little future-proofing by the decoder; the '1' // is actually implicit on a real 8088. So omit it. - if(instruction.operand() == 1) break; + if(instruction.second.operand() == 1) break; [[fallthrough]]; default: operation += ", "; - operation += to_string(instruction.source(), instruction, offset_length, immediate_length); + operation += to_string(instruction.second.source(), instruction.second, offset_length, immediate_length); break; } break; @@ -597,7 +597,7 @@ std::string InstructionSet::x86::to_string( //); template std::string InstructionSet::x86::to_string( - Instruction instruction, + std::pair> instruction, Model model, int offset_length, int immediate_length diff --git a/InstructionSets/x86/Instruction.hpp b/InstructionSets/x86/Instruction.hpp index 9f8b4995b..87e5ff358 100644 --- a/InstructionSets/x86/Instruction.hpp +++ b/InstructionSets/x86/Instruction.hpp @@ -929,7 +929,7 @@ std::string to_string( /// If @c immediate_length is '2' or '4', truncates any printed immediate value to 2 or 4 digits if it is compatible with being that length. template std::string to_string( - Instruction instruction, + std::pair> instruction, Model model, int offset_length = 0, int immediate_length = 0); diff --git a/OSBindings/Mac/Clock SignalTests/8088Tests.mm b/OSBindings/Mac/Clock SignalTests/8088Tests.mm index e27e8a67b..28bee7fb8 100644 --- a/OSBindings/Mac/Clock SignalTests/8088Tests.mm +++ b/OSBindings/Mac/Clock SignalTests/8088Tests.mm @@ -398,7 +398,7 @@ struct FailedExecution { return [NSJSONSerialization JSONObjectWithData:[NSData dataWithContentsOfGZippedFile:path] options:0 error:nil]; } -- (NSString *)toString:(const InstructionSet::x86::Instruction &)instruction offsetLength:(int)offsetLength immediateLength:(int)immediateLength { +- (NSString *)toString:(const std::pair> &)instruction offsetLength:(int)offsetLength immediateLength:(int)immediateLength { const auto operation = to_string(instruction, InstructionSet::x86::Model::i8086, offsetLength, immediateLength); return [[NSString stringWithUTF8String:operation.c_str()] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; } @@ -445,12 +445,12 @@ struct FailedExecution { // The decoder doesn't preserve the original offset length, which makes no functional difference but // does affect the way that offsets are printed in the test set. NSSet *decodings = [NSSet setWithObjects: - [self toString:decoded.second offsetLength:4 immediateLength:4], - [self toString:decoded.second offsetLength:2 immediateLength:4], - [self toString:decoded.second offsetLength:0 immediateLength:4], - [self toString:decoded.second offsetLength:4 immediateLength:2], - [self toString:decoded.second offsetLength:2 immediateLength:2], - [self toString:decoded.second offsetLength:0 immediateLength:2], + [self toString:decoded offsetLength:4 immediateLength:4], + [self toString:decoded offsetLength:2 immediateLength:4], + [self toString:decoded offsetLength:0 immediateLength:4], + [self toString:decoded offsetLength:4 immediateLength:2], + [self toString:decoded offsetLength:2 immediateLength:2], + [self toString:decoded offsetLength:0 immediateLength:2], nil]; auto compare_decoding = [&](NSString *name) -> bool { From 0c09c14baa13071d8915de25d5b53043782384b8 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 25 Oct 2023 13:02:43 -0400 Subject: [PATCH 122/128] Incorporate instruction length into offsets. --- InstructionSets/x86/Instruction.cpp | 3 +- OSBindings/Mac/Clock SignalTests/8088Tests.mm | 42 ------------------- 2 files changed, 2 insertions(+), 43 deletions(-) diff --git a/InstructionSets/x86/Instruction.cpp b/InstructionSets/x86/Instruction.cpp index da4a40107..4536089b9 100644 --- a/InstructionSets/x86/Instruction.cpp +++ b/InstructionSets/x86/Instruction.cpp @@ -370,6 +370,7 @@ std::string InstructionSet::x86::to_string( stream << (is_negative ? '-' : '+') << std::uppercase << std::hex << abs_value << 'h'; }; + using Source = InstructionSet::x86::Source; const Source source = pointer.source(); switch(source) { @@ -507,7 +508,7 @@ std::string InstructionSet::x86::to_string( operation += to_string(instruction.second.source(), instruction.second, offset_length, immediate_length); } if(displacement) { - operation += to_hex(instruction.second.displacement(), offset_length); + operation += to_hex(instruction.second.displacement() + instruction.first, offset_length); } } break; diff --git a/OSBindings/Mac/Clock SignalTests/8088Tests.mm b/OSBindings/Mac/Clock SignalTests/8088Tests.mm index 28bee7fb8..e13fd750e 100644 --- a/OSBindings/Mac/Clock SignalTests/8088Tests.mm +++ b/OSBindings/Mac/Clock SignalTests/8088Tests.mm @@ -317,48 +317,6 @@ struct FailedExecution { - (NSArray *)testFiles { NSString *path = [NSString stringWithUTF8String:TestSuiteHome]; NSSet *allowList = [NSSet setWithArray:@[ - // Current decoding failures: - @"60.json.gz", - @"61.json.gz", - @"62.json.gz", - @"63.json.gz", - @"64.json.gz", - @"65.json.gz", - @"66.json.gz", - @"67.json.gz", - @"68.json.gz", - @"69.json.gz", - @"6A.json.gz", - @"6B.json.gz", - @"6C.json.gz", - @"6D.json.gz", - @"6E.json.gz", - @"6F.json.gz", - @"70.json.gz", - @"71.json.gz", - @"72.json.gz", - @"73.json.gz", - @"74.json.gz", - @"75.json.gz", - @"76.json.gz", - @"77.json.gz", - @"78.json.gz", - @"79.json.gz", - @"7A.json.gz", - @"7B.json.gz", - @"7C.json.gz", - @"7D.json.gz", - @"7E.json.gz", - @"7F.json.gz", - @"E0.json.gz", - @"E1.json.gz", - @"E2.json.gz", - @"E3.json.gz", - @"E8.json.gz", - @"E9.json.gz", - @"EB.json.gz", - - // Current execution failures: // @"27.json.gz", // DAA // @"2F.json.gz", // DAS From 3b62638b303ec6e32c69c31dc3f2599c055dd33b Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 25 Oct 2023 14:43:58 -0400 Subject: [PATCH 123/128] Remove dead DataPointerResolver and extra-conditional version of source(). --- InstructionSets/x86/DataPointerResolver.hpp | 325 ------------------ .../Implementation/PerformImplementation.hpp | 12 +- InstructionSets/x86/Instruction.cpp | 2 +- InstructionSets/x86/Instruction.hpp | 20 +- .../Clock Signal.xcodeproj/project.pbxproj | 6 - .../Clock SignalTests/x86DataPointerTests.mm | 103 ------ .../Mac/Clock SignalTests/x86DecoderTests.mm | 1 - 7 files changed, 14 insertions(+), 455 deletions(-) delete mode 100644 InstructionSets/x86/DataPointerResolver.hpp delete mode 100644 OSBindings/Mac/Clock SignalTests/x86DataPointerTests.mm diff --git a/InstructionSets/x86/DataPointerResolver.hpp b/InstructionSets/x86/DataPointerResolver.hpp deleted file mode 100644 index 028984e37..000000000 --- a/InstructionSets/x86/DataPointerResolver.hpp +++ /dev/null @@ -1,325 +0,0 @@ -// -// DataPointerResolver.hpp -// Clock Signal -// -// Created by Thomas Harte on 24/02/2022. -// Copyright © 2022 Thomas Harte. All rights reserved. -// - -#ifndef DataPointerResolver_hpp -#define DataPointerResolver_hpp - -#include "Instruction.hpp" -#include "Model.hpp" - -#include - -namespace InstructionSet::x86 { - -/// Unlike source, describes only registers, and breaks -/// them down by conventional name — so AL, AH, AX and EAX are all -/// listed separately and uniquely, rather than being eAX+size or -/// eSPorAH with a size of 1. -enum class Register: uint8_t { - // 8-bit registers. - AL, AH, - CL, CH, - DL, DH, - BL, BH, - - // 16-bit registers. - AX, CX, DX, BX, - SP, BP, SI, DI, - ES, CS, SS, DS, - FS, GS, - - // 32-bit registers. - EAX, ECX, EDX, EBX, - ESP, EBP, ESI, EDI, - - // - None -}; - -/// @returns @c true if @c r is the same size as @c DataT; @c false otherwise. -/// @discussion Provided primarily to aid in asserts; if the decoder and resolver are both -/// working then it shouldn't be necessary to test this in register files. -template constexpr bool is_sized(Register r) { - static_assert(sizeof(DataT) == 4 || sizeof(DataT) == 2 || sizeof(DataT) == 1); - - if constexpr (sizeof(DataT) == 4) { - return r >= Register::EAX && r < Register::None; - } - - if constexpr (sizeof(DataT) == 2) { - return r >= Register::AX && r < Register::EAX; - } - - if constexpr (sizeof(DataT) == 1) { - return r >= Register::AL && r < Register::AX; - } - - return false; -} - -/// @returns the proper @c Register given @c source and data of size @c sizeof(DataT), -/// or Register::None if no such register exists (e.g. asking for a 32-bit version of CS). -template constexpr Register register_for_source(Source source) { - static_assert(sizeof(DataT) == 4 || sizeof(DataT) == 2 || sizeof(DataT) == 1); - - if constexpr (sizeof(DataT) == 4) { - switch(source) { - case Source::eAX: return Register::EAX; - case Source::eCX: return Register::ECX; - case Source::eDX: return Register::EDX; - case Source::eBX: return Register::EBX; - case Source::eSPorAH: return Register::ESP; - case Source::eBPorCH: return Register::EBP; - case Source::eSIorDH: return Register::ESI; - case Source::eDIorBH: return Register::EDI; - - default: break; - } - } - - if constexpr (sizeof(DataT) == 2) { - switch(source) { - case Source::eAX: return Register::AX; - case Source::eCX: return Register::CX; - case Source::eDX: return Register::DX; - case Source::eBX: return Register::BX; - case Source::eSPorAH: return Register::SP; - case Source::eBPorCH: return Register::BP; - case Source::eSIorDH: return Register::SI; - case Source::eDIorBH: return Register::DI; - case Source::ES: return Register::ES; - case Source::CS: return Register::CS; - case Source::SS: return Register::SS; - case Source::DS: return Register::DS; - case Source::FS: return Register::FS; - case Source::GS: return Register::GS; - - default: break; - } - } - - if constexpr (sizeof(DataT) == 1) { - switch(source) { - case Source::eAX: return Register::AL; - case Source::eCX: return Register::CL; - case Source::eDX: return Register::DL; - case Source::eBX: return Register::BL; - case Source::eSPorAH: return Register::AH; - case Source::eBPorCH: return Register::CH; - case Source::eSIorDH: return Register::DH; - case Source::eDIorBH: return Register::BH; - - default: break; - } - } - - return Register::None; -} - -/// Reads from or writes to the source or target identified by a DataPointer, relying upon two user-supplied classes: -/// -/// * a register bank; and -/// * a memory pool. -/// -/// The register bank should implement: -/// * `template DataT read()` and -/// * `template void write(DataT)`. -/// -/// Which will be called only with registers and data types that are appropriate to the @c model. -/// -/// The memory pool should implement `template DataT read(Source segment, uint32_t address)` and -/// `template void write(Source segment, uint32_t address, DataT value)`. -template class DataPointerResolver { - public: - public: - /// Reads the data pointed to by @c pointer, referencing @c instruction, @c memory and @c registers as necessary. - template static DataT read( - RegistersT ®isters, - MemoryT &memory, - const Instruction &instruction, - DataPointer pointer); - - /// Writes @c value to the data pointed to by @c pointer, referencing @c instruction, @c memory and @c registers as necessary. - template static void write( - RegistersT ®isters, - MemoryT &memory, - const Instruction &instruction, - DataPointer pointer, - DataT value); - - /// Computes the effective address of @c pointer including any displacement applied by @c instruction. - /// @c pointer must be of type Source::Indirect. - template - static uint32_t effective_address( - RegistersT ®isters, - const Instruction &instruction, - DataPointer pointer); - - private: - template static void access( - RegistersT ®isters, - MemoryT &memory, - const Instruction &instruction, - DataPointer pointer, - DataT &value); -}; - - -// -// Implementation begins here. -// - -template -template DataT DataPointerResolver::read( - RegistersT ®isters, - MemoryT &memory, - const Instruction &instruction, - DataPointer pointer) { - DataT result; - access(registers, memory, instruction, pointer, result); - return result; - } - -template -template void DataPointerResolver::write( - RegistersT ®isters, - MemoryT &memory, - const Instruction &instruction, - DataPointer pointer, - DataT value) { - access(registers, memory, instruction, pointer, value); - } - -#define rw(v, r, is_write) \ - case Source::r: \ - using VType = typename std::remove_reference::type; \ - if constexpr (is_write) { \ - registers.template write(Source::r)>(v); \ - } else { \ - v = registers.template read(Source::r)>(); \ - } \ - break; - -#define ALLREGS(v, i) rw(v, eAX, i); rw(v, eCX, i); \ - rw(v, eDX, i); rw(v, eBX, i); \ - rw(v, eSPorAH, i); rw(v, eBPorCH, i); \ - rw(v, eSIorDH, i); rw(v, eDIorBH, i); \ - rw(v, ES, i); rw(v, CS, i); \ - rw(v, SS, i); rw(v, DS, i); \ - rw(v, FS, i); rw(v, GS, i); - -template -template -uint32_t DataPointerResolver::effective_address( - RegistersT ®isters, - const Instruction &instruction, - DataPointer pointer -) { - using AddressT = typename Instruction::AddressT; - AddressT base = 0, index = 0; - - if constexpr (has_base) { - switch(pointer.base()) { - default: break; - ALLREGS(base, false); - } - } - - switch(pointer.index()) { - default: break; - ALLREGS(index, false); - } - - uint32_t address = index; - if constexpr (model >= Model::i80386) { - address <<= pointer.scale(); - } else { - assert(!pointer.scale()); - } - - // Always compute address as 32-bit. - // TODO: verify use of memory_mask around here. - // Also I think possibly an exception is supposed to be generated - // if the programmer is in 32-bit mode and has asked for 16-bit - // address computation but generated e.g. a 17-bit result. Look into - // that when working on execution. For now the goal is merely decoding - // and this code exists both to verify the presence of all necessary - // fields and to help to explore the best breakdown of storage - // within Instruction. - constexpr uint32_t memory_masks[] = {0x0000'ffff, 0xffff'ffff}; - const uint32_t memory_mask = memory_masks[int(instruction.address_size())]; - address = (address & memory_mask) + (base & memory_mask) + instruction.displacement(); - return address; -} - -template -template void DataPointerResolver::access( - RegistersT ®isters, - MemoryT &memory, - const Instruction &instruction, - DataPointer pointer, - DataT &value) { - const Source source = pointer.source(); - - switch(source) { - default: - if constexpr (!is_write) { - value = 0; - } - return; - - ALLREGS(value, is_write); - - case Source::DirectAddress: - if constexpr(is_write) { - memory.template write(instruction.segment_override(), instruction.displacement(), value); - } else { - value = memory.template read(instruction.segment_override(), instruction.displacement()); - } - break; - case Source::Immediate: - value = DataT(instruction.operand()); - break; - - // TODO: data_segment() will return Source::None if there was no override. - // Fix. - -#define indirect(has_base) { \ - const auto address = effective_address \ - (registers, instruction, pointer); \ - \ - if constexpr (is_write) { \ - memory.template write( \ - instruction.segment_override(), \ - address, \ - value \ - ); \ - } else { \ - value = memory.template read( \ - instruction.segment_override(), \ - address \ - ); \ - } \ -} - case Source::IndirectNoBase: - indirect(false); - break; - - case Source::Indirect: - indirect(true); - break; -#undef indirect - - } - } -#undef ALLREGS -#undef rw - -} - -#endif /* DataPointerResolver_hpp */ diff --git a/InstructionSets/x86/Implementation/PerformImplementation.hpp b/InstructionSets/x86/Implementation/PerformImplementation.hpp index 839bc3386..c87ce0753 100644 --- a/InstructionSets/x86/Implementation/PerformImplementation.hpp +++ b/InstructionSets/x86/Implementation/PerformImplementation.hpp @@ -114,7 +114,7 @@ uint32_t address( RegistersT ®isters, MemoryT &memory ) { - switch(pointer.source()) { + switch(pointer.source()) { default: return 0; case Source::eAX: return *register_(registers); case Source::eCX: return *register_(registers); @@ -844,7 +844,7 @@ void call_far(InstructionT &instruction, // TODO: eliminate 16-bit assumption below. uint16_t source_address = 0; const auto pointer = instruction.destination(); - switch(pointer.template source()) { + switch(pointer.source()) { default: case Source::Immediate: flow_controller.call(instruction.segment(), instruction.offset()); return; @@ -876,7 +876,7 @@ void jump_far(InstructionT &instruction, // TODO: eliminate 16-bit assumption below. uint16_t source_address = 0; const auto pointer = instruction.destination(); - switch(pointer.template source()) { + switch(pointer.source()) { default: case Source::Immediate: flow_controller.jump(instruction.segment(), instruction.offset()); return; @@ -1547,7 +1547,7 @@ template < const auto source = [&]() -> IntT& { return *resolve( instruction, - instruction.source().template source(), + instruction.source().source(), instruction.source(), registers, memory, @@ -1557,7 +1557,7 @@ template < const auto destination = [&]() -> IntT& { return *resolve( instruction, - instruction.destination().template source(), + instruction.destination().source(), instruction.destination(), registers, memory, @@ -1576,7 +1576,7 @@ template < const auto shift_count = [&]() -> uint8_t { static constexpr uint8_t mask = (model != Model::i8086) ? 0x1f : 0xff; - switch(instruction.source().template source()) { + switch(instruction.source().source()) { case Source::None: return 1; case Source::Immediate: return uint8_t(instruction.operand()) & mask; default: return registers.cl() & mask; diff --git a/InstructionSets/x86/Instruction.cpp b/InstructionSets/x86/Instruction.cpp index 4536089b9..91a8b5e31 100644 --- a/InstructionSets/x86/Instruction.cpp +++ b/InstructionSets/x86/Instruction.cpp @@ -372,7 +372,7 @@ std::string InstructionSet::x86::to_string( }; using Source = InstructionSet::x86::Source; - const Source source = pointer.source(); + const Source source = pointer.source(); switch(source) { // to_string handles all direct register names correctly. default: return InstructionSet::x86::to_string(source, operation_size); diff --git a/InstructionSets/x86/Instruction.hpp b/InstructionSets/x86/Instruction.hpp index 87e5ff358..e8ecc28b9 100644 --- a/InstructionSets/x86/Instruction.hpp +++ b/InstructionSets/x86/Instruction.hpp @@ -595,10 +595,7 @@ class DataPointer { ); } - template constexpr Source source() const { - if constexpr (obscure_indirectNoBase) { - return (source_ >= Source::IndirectNoBase) ? Source::Indirect : source_; - } + constexpr Source source() const { return source_; } @@ -634,10 +631,7 @@ class DataPointer { return Source::DS; } - template constexpr Source base() const { - if constexpr (obscure_indirectNoBase) { - return (source_ <= Source::IndirectNoBase) ? Source::None : sib_.base(); - } + constexpr Source base() const { return sib_.base(); } @@ -805,11 +799,11 @@ template class Instruction { return DataSize(source_data_dest_sib_ >> 14); } - int length() const { - const int short_length = (source_data_dest_sib_ >> 10) & 15; - if(short_length) return short_length; - return length_extension() >> 6; - } +// int length() const { +// const int short_length = (source_data_dest_sib_ >> 10) & 15; +// if(short_length) return short_length; +// return length_extension() >> 6; +// } ImmediateT operand() const { const ImmediateT ops[] = {0, operand_extension()}; diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index a7ef975c3..4cbaa5fbd 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -1048,7 +1048,6 @@ 4BE21219253FCE9C00435408 /* AppleIIgs.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BE21214253FCE9C00435408 /* AppleIIgs.cpp */; }; 4BE2121A253FCE9C00435408 /* AppleIIgs.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BE21214253FCE9C00435408 /* AppleIIgs.cpp */; }; 4BE34438238389E10058E78F /* AtariSTVideoTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BE34437238389E10058E78F /* AtariSTVideoTests.mm */; }; - 4BE3C69727CC32DC000EAD28 /* x86DataPointerTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BE3C69627CC32DC000EAD28 /* x86DataPointerTests.mm */; }; 4BE76CF922641ED400ACD6FA /* QLTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BE76CF822641ED300ACD6FA /* QLTests.mm */; }; 4BE8EB6625C750B50040BC40 /* DAT.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BE8EB6425C750B50040BC40 /* DAT.cpp */; }; 4BE90FFD22D5864800FB464D /* MacintoshVideoTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BE90FFC22D5864800FB464D /* MacintoshVideoTests.mm */; }; @@ -2214,9 +2213,7 @@ 4BE3231520532AA7006EF799 /* Target.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Target.hpp; sourceTree = ""; }; 4BE3231620532BED006EF799 /* Target.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Target.hpp; sourceTree = ""; }; 4BE34437238389E10058E78F /* AtariSTVideoTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = AtariSTVideoTests.mm; sourceTree = ""; }; - 4BE3C69327C793EF000EAD28 /* DataPointerResolver.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = DataPointerResolver.hpp; sourceTree = ""; }; 4BE3C69527CBC540000EAD28 /* Model.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Model.hpp; sourceTree = ""; }; - 4BE3C69627CC32DC000EAD28 /* x86DataPointerTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = x86DataPointerTests.mm; sourceTree = ""; }; 4BE76CF822641ED300ACD6FA /* QLTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = QLTests.mm; sourceTree = ""; }; 4BE845201F2FF7F100A5EA22 /* CRTC6845.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CRTC6845.hpp; sourceTree = ""; }; 4BE8EB5425C0E9D40040BC40 /* Disassembler.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Disassembler.hpp; sourceTree = ""; }; @@ -4393,7 +4390,6 @@ 4B8DD3672633B2D400B3C866 /* SpectrumVideoContentionTests.mm */, 4B2AF8681E513FC20027EE29 /* TIATests.mm */, 4B1D08051E0F7A1100763741 /* TimeTests.mm */, - 4BE3C69627CC32DC000EAD28 /* x86DataPointerTests.mm */, 4BEE4BD325A26E2B00011BD2 /* x86DecoderTests.mm */, 4BDA8234261E8E000021AA19 /* Z80ContentionTests.mm */, 4BB73EB81B587A5100552FC2 /* Info.plist */, @@ -5001,7 +4997,6 @@ children = ( 4BEDA3B925B25563000C2DBD /* Decoder.cpp */, 4B69DEB52AB79E4F0055B217 /* Instruction.cpp */, - 4BE3C69327C793EF000EAD28 /* DataPointerResolver.hpp */, 4BEDA3B825B25563000C2DBD /* Decoder.hpp */, 4BEDA3DB25B2588F000C2DBD /* Instruction.hpp */, 4BE3C69527CBC540000EAD28 /* Model.hpp */, @@ -6251,7 +6246,6 @@ 4B7752AA28217E370073E2C5 /* ROMCatalogue.cpp in Sources */, 4B778F0323A5EBB00000D260 /* FAT12.cpp in Sources */, 4B778F4023A5F1910000D260 /* z8530.cpp in Sources */, - 4BE3C69727CC32DC000EAD28 /* x86DataPointerTests.mm in Sources */, 4B778EFD23A5EB8E0000D260 /* AppleDSK.cpp in Sources */, 4B7752B728217EF40073E2C5 /* Chipset.cpp in Sources */, 4B778EFB23A5EB7E0000D260 /* HFE.cpp in Sources */, diff --git a/OSBindings/Mac/Clock SignalTests/x86DataPointerTests.mm b/OSBindings/Mac/Clock SignalTests/x86DataPointerTests.mm deleted file mode 100644 index 8db794516..000000000 --- a/OSBindings/Mac/Clock SignalTests/x86DataPointerTests.mm +++ /dev/null @@ -1,103 +0,0 @@ -// -// x86DataPointerTests.m -// Clock Signal -// -// Created by Thomas Harte on 27/02/2022. -// Copyright 2022 Thomas Harte. All rights reserved. -// - -#import - -#include "../../../InstructionSets/x86/DataPointerResolver.hpp" -#include - -using namespace InstructionSet::x86; - -@interface x86DataPointerTests : XCTestCase -@end - -@implementation x86DataPointerTests - -- (void)test16bitSize1 { - const DataPointer indirectPointer( - Source::eAX, Source::eDI, 0 - ); - const DataPointer registerPointer( - Source::eBX - ); - - struct Registers { - uint16_t ax = 0x1234, di = 0x00ee; - uint8_t bl = 0xaa; - - template DataT read() { - assert(is_sized(r)); - switch(r) { - case Register::AX: return ax; - case Register::BL: return bl; - case Register::DI: return di; - default: return 0; - } - } - template void write(DataT value) { - assert(is_sized(r)); - switch(r) { - case Register::BL: bl = value; break; - default: assert(false); - } - } - } registers; - - struct Memory { - std::map data; - - template DataT read(Source, uint32_t address) { - if(address == 0x1234 + 0x00ee) return 0xff; - return 0; - } - template void write(Source, uint32_t address, DataT value) { - data[address] = value; - } - } memory; - - // TODO: construct this more formally; the code below just assumes size = 1, which is not a contractual guarantee. - const auto instruction = Instruction(); - - using Resolver = DataPointerResolver; - const uint8_t memoryValue = Resolver::read( - registers, - memory, - instruction, - indirectPointer - ); - registers.ax = 0x0100; - Resolver::write( - registers, - memory, - instruction, - indirectPointer, - 0xef - ); - - XCTAssertEqual(memoryValue, 0xff); - XCTAssertEqual(memory.data[0x01ee], 0xef); - - const uint8_t registerValue = Resolver::read( - registers, - memory, - instruction, - registerPointer - ); - Resolver::write( - registers, - memory, - instruction, - registerPointer, - 0x93 - ); - - XCTAssertEqual(registerValue, 0xaa); - XCTAssertEqual(registers.bl, 0x93); -} - -@end diff --git a/OSBindings/Mac/Clock SignalTests/x86DecoderTests.mm b/OSBindings/Mac/Clock SignalTests/x86DecoderTests.mm index 26bf5b2d2..73ef9698c 100644 --- a/OSBindings/Mac/Clock SignalTests/x86DecoderTests.mm +++ b/OSBindings/Mac/Clock SignalTests/x86DecoderTests.mm @@ -12,7 +12,6 @@ #include #include #include "../../../InstructionSets/x86/Decoder.hpp" -#include "../../../InstructionSets/x86/DataPointerResolver.hpp" using namespace InstructionSet::x86; From a2826cdee5730e21babfd3aa356ee351a360b9eb Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 25 Oct 2023 16:00:01 -0400 Subject: [PATCH 124/128] Propagate address size. --- .../Implementation/PerformImplementation.hpp | 56 ++++++++++++++----- InstructionSets/x86/Instruction.hpp | 3 + 2 files changed, 46 insertions(+), 13 deletions(-) diff --git a/InstructionSets/x86/Implementation/PerformImplementation.hpp b/InstructionSets/x86/Implementation/PerformImplementation.hpp index c87ce0753..8e8694f0d 100644 --- a/InstructionSets/x86/Implementation/PerformImplementation.hpp +++ b/InstructionSets/x86/Implementation/PerformImplementation.hpp @@ -1526,6 +1526,7 @@ void in(uint16_t port, IntT &value, IOT &io) { template < Model model, DataSize data_size, + AddressSize address_size, typename InstructionT, typename FlowControllerT, typename RegistersT, @@ -1540,7 +1541,7 @@ template < IOT &io ) { using IntT = typename DataSizeType::type; - using AddressT = uint16_t; // TODO. + using AddressT = typename AddressSizeType::type; // Establish source() and destination() shorthand to fetch data if necessary. IntT immediate; @@ -1820,22 +1821,51 @@ template < // TODO: incorporate and propagate address size. - switch(instruction.operation_size()) { - case DataSize::Byte: - perform(instruction, status, flow_controller, registers, memory, io); - break; - case DataSize::Word: - perform(instruction, status, flow_controller, registers, memory, io); - break; - case DataSize::DWord: + auto size = [](DataSize operation_size, AddressSize address_size) constexpr -> int { + return int(operation_size) + (int(address_size) << 2); + }; + + switch(size(instruction.operation_size(), instruction.address_size())) { + // 16-bit combinations. + case size(DataSize::Byte, AddressSize::b16): + perform(instruction, status, flow_controller, registers, memory, io); + return; + case size(DataSize::Word, AddressSize::b16): + perform(instruction, status, flow_controller, registers, memory, io); + return; + + // 32-bit combinations. + case size(DataSize::Byte, AddressSize::b32): if constexpr (is_32bit(model)) { - perform(instruction, status, flow_controller, registers, memory, io); + perform(instruction, status, flow_controller, registers, memory, io); + return; } - [[fallthrough]]; - case DataSize::None: - assert(false); break; + case size(DataSize::Word, AddressSize::b32): + if constexpr (is_32bit(model)) { + perform(instruction, status, flow_controller, registers, memory, io); + return; + } + break; + case size(DataSize::DWord, AddressSize::b16): + if constexpr (is_32bit(model)) { + perform(instruction, status, flow_controller, registers, memory, io); + return; + } + break; + case size(DataSize::DWord, AddressSize::b32): + if constexpr (is_32bit(model)) { + perform(instruction, status, flow_controller, registers, memory, io); + return; + } + break; + + default: break; } + + // This is reachable only if the data and address size combination in use isn't available on the processor + // model nominated. + assert(false); } } diff --git a/InstructionSets/x86/Instruction.hpp b/InstructionSets/x86/Instruction.hpp index e8ecc28b9..7a95dd540 100644 --- a/InstructionSets/x86/Instruction.hpp +++ b/InstructionSets/x86/Instruction.hpp @@ -387,6 +387,9 @@ enum class AddressSize: uint8_t { b32 = 1, }; +template struct AddressSizeType { using type = uint16_t; }; +template <> struct AddressSizeType { using type = uint32_t; }; + constexpr DataSize data_size(AddressSize size) { return DataSize(int(size) + 1); } From 29a921f764d7688dfeed554526d97b511961ce2b Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 25 Oct 2023 16:15:08 -0400 Subject: [PATCH 125/128] Remove TODO, add exposition. --- .../x86/Implementation/PerformImplementation.hpp | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/InstructionSets/x86/Implementation/PerformImplementation.hpp b/InstructionSets/x86/Implementation/PerformImplementation.hpp index 8e8694f0d..eb76cc5fb 100644 --- a/InstructionSets/x86/Implementation/PerformImplementation.hpp +++ b/InstructionSets/x86/Implementation/PerformImplementation.hpp @@ -1815,16 +1815,11 @@ template < MemoryT &memory, IOT &io ) { - // Dispatch to a function just like this that is specialised on data size. - // Fetching will occur in that specialised function, per the overlapping - // meaning of register names. - - // TODO: incorporate and propagate address size. - auto size = [](DataSize operation_size, AddressSize address_size) constexpr -> int { return int(operation_size) + (int(address_size) << 2); }; + // Dispatch to a function specialised on data and address size. switch(size(instruction.operation_size(), instruction.address_size())) { // 16-bit combinations. case size(DataSize::Byte, AddressSize::b16): @@ -1835,6 +1830,10 @@ template < return; // 32-bit combinations. + // + // The if constexprs below ensure that `perform` isn't compiled for incompatible data or address size and + // model combinations. So if a caller nominates a 16-bit model it can supply registers and memory objects + // that don't implement 32-bit registers or accesses. case size(DataSize::Byte, AddressSize::b32): if constexpr (is_32bit(model)) { perform(instruction, status, flow_controller, registers, memory, io); @@ -1863,8 +1862,8 @@ template < default: break; } - // This is reachable only if the data and address size combination in use isn't available on the processor - // model nominated. + // This is reachable only if the data and address size combination in use isn't available + // on the processor model nominated. assert(false); } From de230fb6be3674d0f76f8fb4aa74e036a431ea7e Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 25 Oct 2023 22:21:23 -0400 Subject: [PATCH 126/128] Resolve for work factored out. --- .../Implementation/PerformImplementation.hpp | 70 ++++++------------- 1 file changed, 22 insertions(+), 48 deletions(-) diff --git a/InstructionSets/M68k/Implementation/PerformImplementation.hpp b/InstructionSets/M68k/Implementation/PerformImplementation.hpp index c5f7fda1c..95a484e30 100644 --- a/InstructionSets/M68k/Implementation/PerformImplementation.hpp +++ b/InstructionSets/M68k/Implementation/PerformImplementation.hpp @@ -9,6 +9,7 @@ #ifndef InstructionSets_M68k_PerformImplementation_h #define InstructionSets_M68k_PerformImplementation_h +#include "../../../Numeric/Carry.hpp" #include "../ExceptionVectors.hpp" #include @@ -25,28 +26,6 @@ inline int32_t s_extend16(uint16_t x) { return int32_t(int16_t(x)); } namespace Primitive { -/// @returns An int of type @c IntT with only the most-significant bit set. -template constexpr IntT top_bit() { - static_assert(!std::numeric_limits::is_signed); - constexpr IntT max = std::numeric_limits::max(); - return max - (max >> 1); -} - -/// @returns An int with the top bit indicating whether overflow occurred when @c source and @c destination -/// were either added (if @c is_add is true) or subtracted (if @c is_add is false) and the result was @c result. -/// All other bits will be clear. -template -static Status::FlagT overflow(IntT source, IntT destination, IntT result) { - const IntT output_changed = result ^ destination; - const IntT input_differed = source ^ destination; - - if constexpr (is_add) { - return top_bit() & output_changed & ~input_differed; - } else { - return top_bit() & output_changed & input_differed; - } -} - /// Performs an add or subtract (as per @c is_add) between @c source and @c destination, /// updating @c status. @c is_extend indicates whether this is an extend operation (e.g. ADDX) /// or a plain one (e.g. ADD). @@ -81,7 +60,7 @@ static void add_sub(IntT source, IntT &destination, Status &status) { status.zero_result = Status::FlagT(result); } status.set_negative(result); - status.overflow_flag = overflow(source, destination, result); + status.overflow_flag = Numeric::overflow(destination, source, result); destination = result; } @@ -143,7 +122,7 @@ void compare(IntT source, IntT destination, Status &status) { const IntT result = destination - source; status.carry_flag = result > destination; status.set_neg_zero(result); - status.overflow_flag = Primitive::overflow(source, destination, result); + status.overflow_flag = Numeric::overflow(destination, source, result); } /// @returns the name of the bit to be used as a mask for BCLR, BCHG, BSET or BTST for @@ -293,7 +272,7 @@ template void negative(IntT &source, Status &sta } status.extend_flag = status.carry_flag = result; // i.e. any value other than 0 will result in carry. status.set_negative(result); - status.overflow_flag = Primitive::overflow(source, IntT(0), result); + status.overflow_flag = Numeric::overflow(IntT(0), source, result); source = result; } @@ -311,11 +290,6 @@ template int shift_count(uint8_t source return count; } -/// @returns The number of bits in @c IntT. -template constexpr int bit_size() { - return sizeof(IntT) * 8; -} - /// Perform an arithmetic or logical shift, i.e. any of LSL, LSR, ASL or ASR. template void shift(uint32_t source, IntT &destination, Status &status, FlowController &flow_controller) { static_assert( @@ -325,7 +299,7 @@ template void shif operation == Operation::LSRb || operation == Operation::LSRw || operation == Operation::LSRl ); - constexpr auto size = bit_size(); + constexpr auto size = Numeric::bit_size(); const auto shift = shift_count(uint8_t(source), flow_controller); if(!shift) { @@ -355,7 +329,7 @@ template void shif if(shift > size) { status.carry_flag = status.extend_flag = 0; } else { - status.carry_flag = status.extend_flag = (destination << (shift - 1)) & top_bit(); + status.carry_flag = status.extend_flag = (destination << (shift - 1)) & Numeric::top_bit(); } if(type == Type::LSL) { @@ -370,7 +344,7 @@ template void shif // For a shift of n places, overflow will be set if the top n+1 bits were not // all the same value. const auto affected_bits = IntT( - ~((top_bit() >> shift) - 1) + ~((Numeric::top_bit() >> shift) - 1) ); // e.g. shift = 1 => ~((0x80 >> 1) - 1) = ~(0x40 - 1) = ~0x3f = 0xc0, i.e. if shift is // 1 then the top two bits are relevant to whether there was overflow. If they have the // same value, i.e. are both 0 or are both 1, then there wasn't. Otherwise there was. @@ -396,7 +370,7 @@ template void shif const IntT sign_word = type == Type::LSR ? - 0 : (destination & top_bit() ? IntT(~0) : 0); + 0 : (destination & Numeric::top_bit() ? IntT(~0) : 0); if(shift >= size) { destination = sign_word; @@ -417,7 +391,7 @@ template void rota operation == Operation::RORb || operation == Operation::RORw || operation == Operation::RORl ); - constexpr auto size = bit_size(); + constexpr auto size = Numeric::bit_size(); auto shift = shift_count(uint8_t(source), flow_controller); if(!shift) { @@ -442,7 +416,7 @@ template void rota (destination << (size - shift)) ); } - status.carry_flag = Status::FlagT(destination & top_bit()); + status.carry_flag = Status::FlagT(destination & Numeric::top_bit()); break; } } @@ -458,7 +432,7 @@ template void rox( operation == Operation::ROXRb || operation == Operation::ROXRw || operation == Operation::ROXRl ); - constexpr auto size = bit_size(); + constexpr auto size = Numeric::bit_size(); auto shift = shift_count(uint8_t(source), flow_controller) % (size + 1); if(!shift) { @@ -469,9 +443,9 @@ template void rox( case Operation::ROXLb: case Operation::ROXLw: case Operation::ROXLl: status.carry_flag = Status::FlagT((destination >> (size - shift)) & 1); - if(shift == bit_size()) { + if(shift == Numeric::bit_size()) { destination = IntT( - (status.extend_flag ? top_bit() : 0) | + (status.extend_flag ? Numeric::top_bit() : 0) | (destination >> 1) ); } else if(shift == 1) { @@ -491,7 +465,7 @@ template void rox( case Operation::ROXRb: case Operation::ROXRw: case Operation::ROXRl: status.carry_flag = Status::FlagT(destination & (1 << (shift - 1))); - if(shift == bit_size()) { + if(shift == Numeric::bit_size()) { destination = IntT( (status.extend_flag ? 1 : 0) | (destination << 1) @@ -499,12 +473,12 @@ template void rox( } else if(shift == 1) { destination = IntT( (destination >> 1) | - (status.extend_flag ? top_bit() : 0) + (status.extend_flag ? Numeric::top_bit() : 0) ); } else { destination = IntT( (destination >> shift) | - ((status.extend_flag ? top_bit() : 0) >> (shift - 1)) | + ((status.extend_flag ? Numeric::top_bit() : 0) >> (shift - 1)) | (destination << (size + 1 - shift)) ); } @@ -882,14 +856,14 @@ template < Shifts and rotates. */ case Operation::ASLm: - status.extend_flag = status.carry_flag = src.w & Primitive::top_bit(); - status.overflow_flag = (src.w ^ (src.w << 1)) & Primitive::top_bit(); + status.extend_flag = status.carry_flag = src.w & Numeric::top_bit(); + status.overflow_flag = (src.w ^ (src.w << 1)) & Numeric::top_bit(); src.w <<= 1; status.set_neg_zero(src.w); break; case Operation::LSLm: - status.extend_flag = status.carry_flag = src.w & Primitive::top_bit(); + status.extend_flag = status.carry_flag = src.w & Numeric::top_bit(); status.overflow_flag = 0; src.w <<= 1; status.set_neg_zero(src.w); @@ -898,7 +872,7 @@ template < case Operation::ASRm: status.extend_flag = status.carry_flag = src.w & 1; status.overflow_flag = 0; - src.w = (src.w & Primitive::top_bit()) | (src.w >> 1); + src.w = (src.w & Numeric::top_bit()) | (src.w >> 1); status.set_neg_zero(src.w); break; @@ -918,13 +892,13 @@ template < case Operation::RORm: src.w = uint16_t((src.w >> 1) | (src.w << 15)); - status.carry_flag = src.w & Primitive::top_bit(); + status.carry_flag = src.w & Numeric::top_bit(); status.overflow_flag = 0; status.set_neg_zero(src.w); break; case Operation::ROXLm: - status.carry_flag = src.w & Primitive::top_bit(); + status.carry_flag = src.w & Numeric::top_bit(); src.w = uint16_t((src.w << 1) | (status.extend_flag ? 0x0001 : 0x0000)); status.extend_flag = status.carry_flag; status.overflow_flag = 0; From 6ac66dad0c287c526e686296e81508b4cee913c0 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 25 Oct 2023 22:22:20 -0400 Subject: [PATCH 127/128] Remove stale notes. --- Numeric/Carry.hpp | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/Numeric/Carry.hpp b/Numeric/Carry.hpp index 1e28bfdfc..0d4a235fa 100644 --- a/Numeric/Carry.hpp +++ b/Numeric/Carry.hpp @@ -50,13 +50,6 @@ template bool carried_in(IntT lhs, IntT rhs, IntT resul return IntT(1 << bit) & (lhs ^ rhs ^ result); } -// -// BEGIN TEMPORARY COPY AND PASTE SECTION. -// -// The following are largely excised from the M68k PerformImplementation.hpp; if there proves to be no -// reason further to specialise them, there'll be a factoring out. In some cases I've tightened the documentation. -// - /// @returns An int of type @c IntT with only the most-significant bit set. template constexpr IntT top_bit() { static_assert(!std::numeric_limits::is_signed); @@ -84,12 +77,6 @@ IntT overflow(IntT lhs, IntT rhs, IntT result) { return top_bit() & output_changed & input_differed; } } -// NOTE TO FUTURE SELF: the original 68k `overflow` treats lhs and rhs the other way -// around, affecting subtractive overflow. Be careful. - -// -// END COPY AND PASTE SECTION. -// } From dafb134cdc1f9d6b81dcd570f2017097e0ad721e Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 25 Oct 2023 22:27:44 -0400 Subject: [PATCH 128/128] Eliminate dead detour. --- InstructionSets/x86/Decoder.cpp | 24 ++++++------------------ InstructionSets/x86/Decoder.hpp | 3 --- 2 files changed, 6 insertions(+), 21 deletions(-) diff --git a/InstructionSets/x86/Decoder.cpp b/InstructionSets/x86/Decoder.cpp index 81a369e22..21d3370ab 100644 --- a/InstructionSets/x86/Decoder.cpp +++ b/InstructionSets/x86/Decoder.cpp @@ -1014,24 +1014,12 @@ std::pair::InstructionT> Decoder::decode(con if(bytes_to_consume == outstanding_bytes) { phase_ = Phase::ReadyToPost; - // TODO: whether the displacement is signed appears to depend on the opcode. - // Find an appropriate table. - -// if(!sign_extend_displacement_) { -// switch(displacement_size_) { -// case DataSize::None: displacement_ = 0; break; -// case DataSize::Byte: displacement_ = decltype(displacement_)(uint8_t(inward_data_)); break; -// case DataSize::Word: displacement_ = decltype(displacement_)(uint16_t(inward_data_)); break; -// case DataSize::DWord: displacement_ = decltype(displacement_)(uint32_t(inward_data_)); break; -// } -// } else { - switch(displacement_size_) { - case DataSize::None: displacement_ = 0; break; - case DataSize::Byte: displacement_ = int8_t(inward_data_); break; - case DataSize::Word: displacement_ = int16_t(inward_data_); break; - case DataSize::DWord: displacement_ = int32_t(inward_data_); break; - } -// } + switch(displacement_size_) { + case DataSize::None: displacement_ = 0; break; + case DataSize::Byte: displacement_ = int8_t(inward_data_); break; + case DataSize::Word: displacement_ = int16_t(inward_data_); break; + case DataSize::DWord: displacement_ = int32_t(inward_data_); break; + } inward_data_ >>= bit_size(displacement_size_); // Use inequality of sizes as a test for necessary sign extension. diff --git a/InstructionSets/x86/Decoder.hpp b/InstructionSets/x86/Decoder.hpp index 354d4accd..eaaad84e5 100644 --- a/InstructionSets/x86/Decoder.hpp +++ b/InstructionSets/x86/Decoder.hpp @@ -195,8 +195,6 @@ template class Decoder { bool sign_extend_operand_ = false; // If set then sign extend the operand up to the operation size; // otherwise it'll be zero-padded. - bool sign_extend_displacement_ = false; // Much as above; 'displacement' is used internally for both - // displacements and offsets, so signage will vary. // Prefix capture fields. Repetition repetition_ = Repetition::None; @@ -225,7 +223,6 @@ template class Decoder { next_inward_data_shift_ = 0; inward_data_ = 0; sign_extend_operand_ = false; - sign_extend_displacement_ = false; } };