mirror of
https://github.com/TomHarte/CLK.git
synced 2025-08-07 08:26:28 +00:00
Merge pull request #1193 from TomHarte/8088Intentions
Work towards x86 access violations.
This commit is contained in:
@@ -138,7 +138,7 @@ std::pair<int, typename Decoder<model>::InstructionT> Decoder<model>::decode(con
|
||||
|
||||
PartialBlock(0x00, ADD); break;
|
||||
case 0x06: Complete(PUSH, ES, None, data_size_); break;
|
||||
case 0x07: Complete(POP, ES, None, data_size_); break;
|
||||
case 0x07: Complete(POP, None, ES, data_size_); break;
|
||||
|
||||
PartialBlock(0x08, OR); break;
|
||||
case 0x0e: Complete(PUSH, CS, None, data_size_); break;
|
||||
@@ -147,7 +147,7 @@ std::pair<int, typename Decoder<model>::InstructionT> Decoder<model>::decode(con
|
||||
// prefixed with $0f.
|
||||
case 0x0f:
|
||||
if constexpr (model < Model::i80286) {
|
||||
Complete(POP, CS, None, data_size_);
|
||||
Complete(POP, None, CS, data_size_);
|
||||
} else {
|
||||
phase_ = Phase::InstructionPageF;
|
||||
}
|
||||
@@ -155,11 +155,11 @@ std::pair<int, typename Decoder<model>::InstructionT> Decoder<model>::decode(con
|
||||
|
||||
PartialBlock(0x10, ADC); break;
|
||||
case 0x16: Complete(PUSH, SS, None, DataSize::Word); break;
|
||||
case 0x17: Complete(POP, SS, None, DataSize::Word); break;
|
||||
case 0x17: Complete(POP, None, SS, DataSize::Word); break;
|
||||
|
||||
PartialBlock(0x18, SBB); break;
|
||||
case 0x1e: Complete(PUSH, DS, None, DataSize::Word); break;
|
||||
case 0x1f: Complete(POP, DS, None, DataSize::Word); break;
|
||||
case 0x1f: Complete(POP, None, DS, DataSize::Word); break;
|
||||
|
||||
PartialBlock(0x20, AND); break;
|
||||
case 0x26: segment_override_ = Source::ES; break;
|
||||
@@ -523,6 +523,7 @@ std::pair<int, typename Decoder<model>::InstructionT> Decoder<model>::decode(con
|
||||
|
||||
// MARK: - Additional F page of instructions.
|
||||
|
||||
if constexpr (model >= Model::i80286) {
|
||||
if(phase_ == Phase::InstructionPageF && source != end) {
|
||||
// Update the instruction acquired.
|
||||
const uint8_t instr = *source;
|
||||
@@ -610,7 +611,7 @@ std::pair<int, typename Decoder<model>::InstructionT> Decoder<model>::decode(con
|
||||
#undef Set
|
||||
|
||||
case 0xa0: RequiresMin(i80386); Complete(PUSH, FS, None, data_size_); break;
|
||||
case 0xa1: RequiresMin(i80386); Complete(POP, FS, None, data_size_); break;
|
||||
case 0xa1: RequiresMin(i80386); Complete(POP, None, FS, data_size_); break;
|
||||
case 0xa3: RequiresMin(i80386); MemRegReg(BT, MemReg_Reg, data_size_); break;
|
||||
case 0xa4:
|
||||
RequiresMin(i80386);
|
||||
@@ -622,7 +623,7 @@ std::pair<int, typename Decoder<model>::InstructionT> Decoder<model>::decode(con
|
||||
MemRegReg(SHLDCL, MemReg_Reg, data_size_);
|
||||
break;
|
||||
case 0xa8: RequiresMin(i80386); Complete(PUSH, GS, None, data_size_); break;
|
||||
case 0xa9: RequiresMin(i80386); Complete(POP, GS, None, data_size_); break;
|
||||
case 0xa9: RequiresMin(i80386); Complete(POP, None, GS, data_size_); break;
|
||||
case 0xab: RequiresMin(i80386); MemRegReg(BTS, MemReg_Reg, data_size_); break;
|
||||
case 0xac:
|
||||
RequiresMin(i80386);
|
||||
@@ -664,6 +665,7 @@ std::pair<int, typename Decoder<model>::InstructionT> Decoder<model>::decode(con
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#undef Requires
|
||||
#undef RequiresMin
|
||||
@@ -979,6 +981,7 @@ std::pair<int, typename Decoder<model>::InstructionT> Decoder<model>::decode(con
|
||||
|
||||
// MARK: - ScaleIndexBase
|
||||
|
||||
if constexpr (is_32bit(model)) {
|
||||
if(phase_ == Phase::ScaleIndexBase && source != end) {
|
||||
sib_ = *source;
|
||||
++source;
|
||||
@@ -992,6 +995,7 @@ std::pair<int, typename Decoder<model>::InstructionT> Decoder<model>::decode(con
|
||||
|
||||
phase_ = (displacement_size_ != DataSize::None || operand_size_ != DataSize::None) ? Phase::DisplacementOrOperand : Phase::ReadyToPost;
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Displacement and operand.
|
||||
|
||||
@@ -1041,6 +1045,18 @@ std::pair<int, typename Decoder<model>::InstructionT> Decoder<model>::decode(con
|
||||
// MARK: - Check for completion.
|
||||
|
||||
if(phase_ == Phase::ReadyToPost) {
|
||||
// TODO: map to #UD where applicable; build LOCK into the Operation type, buying an extra bit for the operation?
|
||||
//
|
||||
// As of the P6 Intel stipulates that:
|
||||
//
|
||||
// "The LOCK prefix can be prepended only to the following instructions and to those forms of the instructions
|
||||
// that use a memory operand: ADD, ADC, AND, BTC, BTR, BTS, CMPXCHG, DEC, INC, NEG, NOT, OR, SBB, SUB, XOR,
|
||||
// XADD, and XCHG."
|
||||
//
|
||||
// ... and the #UD exception will be raised if LOCK is encountered elsewhere. So adding 17 additional
|
||||
// operations would unlock an extra bit of storage for a net gain of 239 extra operation types and thereby
|
||||
// alleviating any concerns over whether there'll be space to handle MMX, floating point extensions, etc.
|
||||
|
||||
const auto result = std::make_pair(
|
||||
consumed_,
|
||||
InstructionT(
|
||||
|
File diff suppressed because it is too large
Load Diff
@@ -163,7 +163,7 @@ enum class Operation: uint8_t {
|
||||
XOR,
|
||||
/// NOP; no further fields.
|
||||
NOP,
|
||||
/// POP from the stack to source.
|
||||
/// POP from the stack to destination.
|
||||
POP,
|
||||
/// POP from the stack to the flags register.
|
||||
POPF,
|
||||
|
@@ -15,24 +15,59 @@
|
||||
|
||||
namespace InstructionSet::x86 {
|
||||
|
||||
/// Explains the type of access that `perform` intends to perform; is provided as a template parameter to whatever
|
||||
/// the caller supplies as `MemoryT` and `RegistersT` when obtaining a reference to whatever the processor
|
||||
/// intends to reference.
|
||||
///
|
||||
/// `perform` guarantees to validate all accesses before modifying any state, giving the caller opportunity to generate
|
||||
/// any exceptions that might be applicable.
|
||||
enum class AccessType {
|
||||
/// The requested value will be read from.
|
||||
Read,
|
||||
/// The requested value will be written to.
|
||||
Write,
|
||||
/// The requested value will be read from and then written to.
|
||||
ReadModifyWrite,
|
||||
/// The requested value has already been authorised for whatever form of access is now intended, so there's no
|
||||
/// need further to inspect. This is done e.g. by operations that will push multiple values to the stack to verify that
|
||||
/// all necessary stack space is available ahead of pushing anything, though each individual push will then result in
|
||||
/// a further `Preauthorised` access.
|
||||
PreauthorisedRead,
|
||||
PreauthorisedWrite,
|
||||
};
|
||||
|
||||
template <
|
||||
Model model_,
|
||||
typename FlowControllerT,
|
||||
typename RegistersT,
|
||||
typename MemoryT,
|
||||
typename IOT
|
||||
> struct ExecutionContext {
|
||||
FlowControllerT flow_controller;
|
||||
Status status;
|
||||
RegistersT registers;
|
||||
MemoryT memory;
|
||||
IOT io;
|
||||
static constexpr Model model = model_;
|
||||
};
|
||||
|
||||
/// Performs @c instruction querying @c registers and/or @c memory as required, using @c io for port input/output,
|
||||
/// and providing any flow control effects to @c flow_controller.
|
||||
///
|
||||
/// Any change in processor status will be applied to @c status.
|
||||
template <
|
||||
Model model,
|
||||
typename InstructionT,
|
||||
typename FlowControllerT,
|
||||
typename RegistersT,
|
||||
typename MemoryT,
|
||||
typename IOT
|
||||
typename ContextT
|
||||
> void perform(
|
||||
const InstructionT &instruction,
|
||||
Status &status,
|
||||
FlowControllerT &flow_controller,
|
||||
RegistersT ®isters,
|
||||
MemoryT &memory,
|
||||
IOT &io
|
||||
ContextT &context
|
||||
);
|
||||
|
||||
template <
|
||||
typename ContextT
|
||||
> void interrupt(
|
||||
int index,
|
||||
ContextT &context
|
||||
);
|
||||
|
||||
}
|
||||
|
@@ -14,6 +14,8 @@
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <fstream>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
|
||||
#include "NSData+dataWithContentsOfGZippedFile.h"
|
||||
|
||||
@@ -91,22 +93,26 @@ struct Registers {
|
||||
}
|
||||
};
|
||||
struct Memory {
|
||||
enum class Tag {
|
||||
Seeded,
|
||||
AccessExpected,
|
||||
Accessed,
|
||||
FlagsL,
|
||||
FlagsH
|
||||
};
|
||||
public:
|
||||
using AccessType = InstructionSet::x86::AccessType;
|
||||
|
||||
std::unordered_map<uint32_t, Tag> tags;
|
||||
std::vector<uint8_t> memory;
|
||||
const Registers ®isters_;
|
||||
template <typename IntT, AccessType type> struct ReturnType;
|
||||
|
||||
// Reads: return a value directly.
|
||||
template <typename IntT> struct ReturnType<IntT, AccessType::Read> { using type = IntT; };
|
||||
template <typename IntT> struct ReturnType<IntT, AccessType::PreauthorisedRead> { using type = IntT; };
|
||||
|
||||
// Writes: return a reference.
|
||||
template <typename IntT> struct ReturnType<IntT, AccessType::Write> { using type = IntT &; };
|
||||
template <typename IntT> struct ReturnType<IntT, AccessType::ReadModifyWrite> { using type = IntT &; };
|
||||
template <typename IntT> struct ReturnType<IntT, AccessType::PreauthorisedWrite> { using type = IntT &; };
|
||||
|
||||
// Constructor.
|
||||
Memory(Registers ®isters) : registers_(registers) {
|
||||
memory.resize(1024*1024);
|
||||
}
|
||||
|
||||
// Initialisation.
|
||||
void clear() {
|
||||
tags.clear();
|
||||
}
|
||||
@@ -120,6 +126,103 @@ struct Memory {
|
||||
tags[address] = Tag::AccessExpected;
|
||||
}
|
||||
|
||||
// Preauthorisation call-ins.
|
||||
void preauthorise_stack_write(uint32_t length) {
|
||||
uint16_t sp = registers_.sp_;
|
||||
while(length--) {
|
||||
--sp;
|
||||
preauthorise(InstructionSet::x86::Source::SS, sp);
|
||||
}
|
||||
}
|
||||
void preauthorise_stack_read(uint32_t length) {
|
||||
uint16_t sp = registers_.sp_;
|
||||
while(length--) {
|
||||
preauthorise(InstructionSet::x86::Source::SS, sp);
|
||||
++sp;
|
||||
}
|
||||
}
|
||||
void preauthorise_read(InstructionSet::x86::Source segment, uint16_t start, uint32_t length) {
|
||||
while(length--) {
|
||||
preauthorise(segment, start);
|
||||
++start;
|
||||
}
|
||||
}
|
||||
void preauthorise_read(uint32_t start, uint32_t length) {
|
||||
while(length--) {
|
||||
preauthorise(start);
|
||||
++start;
|
||||
}
|
||||
}
|
||||
|
||||
// Access call-ins.
|
||||
|
||||
// Accesses an address based on segment:offset.
|
||||
template <typename IntT, AccessType type>
|
||||
typename ReturnType<IntT, type>::type &access(InstructionSet::x86::Source segment, uint32_t address) {
|
||||
if constexpr (std::is_same_v<IntT, uint16_t>) {
|
||||
// If this is a 16-bit access that runs past the end of the segment, it'll wrap back
|
||||
// to the start. So the 16-bit value will need to be a local cache.
|
||||
if(address == 0xffff) {
|
||||
write_back_address_[0] = (segment_base(segment) + address) & 0xf'ffff;
|
||||
write_back_address_[1] = (write_back_address_[0] - 65535) & 0xf'ffff;
|
||||
write_back_value_ = memory[write_back_address_[0]] | (memory[write_back_address_[1]] << 8);
|
||||
return write_back_value_;
|
||||
}
|
||||
}
|
||||
auto &value = access<IntT, type>(segment, address, Tag::Accessed);
|
||||
|
||||
// If the CPU has indicated a write, it should be safe to fuzz the value now.
|
||||
if(type == AccessType::Write) {
|
||||
value = IntT(~0);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
// Accesses an address based on physical location.
|
||||
template <typename IntT, AccessType type>
|
||||
typename ReturnType<IntT, type>::type &access(uint32_t address) {
|
||||
return access<IntT, type>(address, Tag::Accessed);
|
||||
}
|
||||
|
||||
template <typename IntT>
|
||||
void write_back() {
|
||||
if constexpr (std::is_same_v<IntT, uint16_t>) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
enum class Tag {
|
||||
Seeded,
|
||||
AccessExpected,
|
||||
Accessed,
|
||||
};
|
||||
|
||||
std::unordered_set<uint32_t> preauthorisations;
|
||||
std::unordered_map<uint32_t, Tag> tags;
|
||||
std::vector<uint8_t> memory;
|
||||
const Registers ®isters_;
|
||||
|
||||
void preauthorise(uint32_t address) {
|
||||
preauthorisations.insert(address);
|
||||
}
|
||||
void preauthorise(InstructionSet::x86::Source segment, uint16_t address) {
|
||||
preauthorise((segment_base(segment) + address) & 0xf'ffff);
|
||||
}
|
||||
bool test_preauthorisation(uint32_t address) {
|
||||
auto authorisation = preauthorisations.find(address);
|
||||
if(authorisation == preauthorisations.end()) {
|
||||
return false;
|
||||
}
|
||||
preauthorisations.erase(authorisation);
|
||||
return true;
|
||||
}
|
||||
|
||||
uint32_t segment_base(InstructionSet::x86::Source segment) {
|
||||
uint32_t physical_address;
|
||||
using Source = InstructionSet::x86::Source;
|
||||
@@ -132,66 +235,48 @@ struct Memory {
|
||||
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 <typename IntT> IntT &access(InstructionSet::x86::Source segment, uint16_t address, Tag tag) {
|
||||
template <typename IntT, AccessType type>
|
||||
typename ReturnType<IntT, type>::type &access(InstructionSet::x86::Source segment, uint16_t address, Tag tag) {
|
||||
const uint32_t physical_address = (segment_base(segment) + address) & 0xf'ffff;
|
||||
return access<IntT>(physical_address, tag);
|
||||
return access<IntT, type>(physical_address, tag);
|
||||
}
|
||||
|
||||
// An additional entry point for the flow controller; on the original 8086 interrupt vectors aren't relative
|
||||
// to a selector, they're just at an absolute location.
|
||||
template <typename IntT> IntT &access(uint32_t address, Tag tag) {
|
||||
template <typename IntT, AccessType type>
|
||||
typename ReturnType<IntT, type>::type &access(uint32_t address, Tag tag) {
|
||||
if constexpr (type == AccessType::PreauthorisedRead || type == AccessType::PreauthorisedWrite) {
|
||||
if(!test_preauthorisation(address)) {
|
||||
printf("Non preauthorised access\n");
|
||||
}
|
||||
}
|
||||
|
||||
// Check for address wraparound
|
||||
if(address >= 0x10'0001 - sizeof(IntT)) {
|
||||
if(address > 0x10'0000 - sizeof(IntT)) {
|
||||
if constexpr (std::is_same_v<IntT, uint8_t>) {
|
||||
address &= 0xf'ffff;
|
||||
} else {
|
||||
address &= 0xf'ffff;
|
||||
if(address == 0xf'ffff) {
|
||||
// This is a 16-bit access comprising the final byte in memory and the first.
|
||||
write_back_address_[0] = address;
|
||||
write_back_address_[1] = 0;
|
||||
write_back_value_ = memory[write_back_address_[0]] | (memory[write_back_address_[1]] << 8);
|
||||
return write_back_value_;
|
||||
} else {
|
||||
address &= 0xf'ffff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(tags.find(address) == tags.end()) {
|
||||
printf("Access to unexpected RAM address");
|
||||
printf("Access to unexpected RAM address\n");
|
||||
}
|
||||
tags[address] = tag;
|
||||
return *reinterpret_cast<IntT *>(&memory[address]);
|
||||
}
|
||||
|
||||
// Entry point for the 8086; simply notes that memory was accessed.
|
||||
template <typename IntT> IntT &access([[maybe_unused]] InstructionSet::x86::Source segment, uint32_t address) {
|
||||
if constexpr (std::is_same_v<IntT, uint16_t>) {
|
||||
// If this is a 16-bit access that runs past the end of the segment, it'll wrap back
|
||||
// to the start. So the 16-bit value will need to be a local cache.
|
||||
if(address == 0xffff) {
|
||||
write_back_address_[0] = (segment_base(segment) + address) & 0xf'ffff;
|
||||
write_back_address_[1] = (write_back_address_[0] - 65535) & 0xf'ffff;
|
||||
write_back_value_ = memory[write_back_address_[0]] | (memory[write_back_address_[1]] << 8);
|
||||
return write_back_value_;
|
||||
}
|
||||
}
|
||||
return access<IntT>(segment, address, Tag::Accessed);
|
||||
}
|
||||
|
||||
template <typename IntT>
|
||||
void write_back() {
|
||||
if constexpr (std::is_same_v<IntT, uint16_t>) {
|
||||
if(write_back_address_[0] != NoWriteBack) {
|
||||
memory[write_back_address_[0]] = write_back_value_ & 0xff;
|
||||
memory[write_back_address_[1]] = write_back_value_ >> 8;
|
||||
write_back_address_[0] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static constexpr uint32_t NoWriteBack = 0; // A low byte address of 0 can't require write-back.
|
||||
uint32_t write_back_address_[2] = {NoWriteBack, NoWriteBack};
|
||||
uint16_t write_back_value_;
|
||||
@@ -205,39 +290,7 @@ class FlowController {
|
||||
FlowController(Memory &memory, Registers ®isters, Status &status) :
|
||||
memory_(memory), registers_(registers), status_(status) {}
|
||||
|
||||
void did_iret() {}
|
||||
void did_near_ret() {}
|
||||
void did_far_ret() {}
|
||||
|
||||
void interrupt(int index) {
|
||||
const uint16_t address = static_cast<uint16_t>(index) << 2;
|
||||
const uint16_t new_ip = memory_.access<uint16_t>(address, Memory::Tag::Accessed);
|
||||
const uint16_t new_cs = memory_.access<uint16_t>(address + 2, Memory::Tag::Accessed);
|
||||
|
||||
push(status_.get(), true);
|
||||
|
||||
using Flag = InstructionSet::x86::Flag;
|
||||
status_.set_from<Flag::Interrupt, Flag::Trap>(0);
|
||||
|
||||
// Push CS and IP.
|
||||
push(registers_.cs_);
|
||||
push(registers_.ip_);
|
||||
|
||||
registers_.cs_ = new_cs;
|
||||
registers_.ip_ = new_ip;
|
||||
}
|
||||
|
||||
void call(uint16_t address) {
|
||||
push(registers_.ip_);
|
||||
jump(address);
|
||||
}
|
||||
|
||||
void call(uint16_t segment, uint16_t offset) {
|
||||
push(registers_.cs_);
|
||||
push(registers_.ip_);
|
||||
jump(segment, offset);
|
||||
}
|
||||
|
||||
// Requirements for perform.
|
||||
void jump(uint16_t address) {
|
||||
registers_.ip_ = address;
|
||||
}
|
||||
@@ -250,12 +303,14 @@ class FlowController {
|
||||
void halt() {}
|
||||
void wait() {}
|
||||
|
||||
void begin_instruction() {
|
||||
should_repeat_ = false;
|
||||
}
|
||||
void repeat_last() {
|
||||
should_repeat_ = true;
|
||||
}
|
||||
|
||||
// Other actions.
|
||||
void begin_instruction() {
|
||||
should_repeat_ = false;
|
||||
}
|
||||
bool should_repeat() const {
|
||||
return should_repeat_;
|
||||
}
|
||||
@@ -265,23 +320,6 @@ class FlowController {
|
||||
Registers ®isters_;
|
||||
Status &status_;
|
||||
bool should_repeat_ = false;
|
||||
|
||||
void push(uint16_t value, bool is_flags = false) {
|
||||
// Perform the push in two steps because it's possible for SP to underflow, and so that FlagsL and
|
||||
// FlagsH can be set separately.
|
||||
--registers_.sp_;
|
||||
memory_.access<uint8_t>(
|
||||
InstructionSet::x86::Source::SS,
|
||||
registers_.sp_,
|
||||
is_flags ? Memory::Tag::FlagsH : Memory::Tag::Accessed
|
||||
) = value >> 8;
|
||||
--registers_.sp_;
|
||||
memory_.access<uint8_t>(
|
||||
InstructionSet::x86::Source::SS,
|
||||
registers_.sp_,
|
||||
is_flags ? Memory::Tag::FlagsL : Memory::Tag::Accessed
|
||||
) = value & 0xff;
|
||||
}
|
||||
};
|
||||
|
||||
struct ExecutionSupport {
|
||||
@@ -290,8 +328,9 @@ struct ExecutionSupport {
|
||||
Memory memory;
|
||||
FlowController flow_controller;
|
||||
IO io;
|
||||
static constexpr auto model = InstructionSet::x86::Model::i8086;
|
||||
|
||||
ExecutionSupport() : memory(registers), flow_controller(memory, registers, status) {}
|
||||
ExecutionSupport(): memory(registers), flow_controller(memory, registers, status) {}
|
||||
|
||||
void clear() {
|
||||
memory.clear();
|
||||
@@ -310,8 +349,8 @@ struct FailedExecution {
|
||||
@end
|
||||
|
||||
@implementation i8088Tests {
|
||||
ExecutionSupport execution_support;
|
||||
std::vector<FailedExecution> execution_failures;
|
||||
ExecutionSupport execution_support;
|
||||
}
|
||||
|
||||
- (NSArray<NSString *> *)testFiles {
|
||||
@@ -383,6 +422,8 @@ struct FailedExecution {
|
||||
return hexInstruction;
|
||||
};
|
||||
|
||||
EACCES;
|
||||
|
||||
const auto decoded = decoder.decode(data.data(), data.size());
|
||||
const bool sizeMatched = decoded.first == data.size();
|
||||
if(assert) {
|
||||
@@ -522,13 +563,9 @@ struct FailedExecution {
|
||||
execution_support.registers.ip_ += decoded.first;
|
||||
do {
|
||||
execution_support.flow_controller.begin_instruction();
|
||||
InstructionSet::x86::perform<InstructionSet::x86::Model::i8086>(
|
||||
InstructionSet::x86::perform(
|
||||
decoded.second,
|
||||
execution_support.status,
|
||||
execution_support.flow_controller,
|
||||
execution_support.registers,
|
||||
execution_support.memory,
|
||||
execution_support.io
|
||||
execution_support
|
||||
);
|
||||
} while (execution_support.flow_controller.should_repeat());
|
||||
|
||||
@@ -537,21 +574,32 @@ struct FailedExecution {
|
||||
InstructionSet::x86::Status intended_status;
|
||||
|
||||
bool ramEqual = true;
|
||||
int mask_position = 0;
|
||||
for(NSArray<NSNumber *> *ram in final_state[@"ram"]) {
|
||||
const uint32_t address = [ram[0] intValue];
|
||||
const auto value = execution_support.memory.access<uint8_t, Memory::AccessType::Read>(address);
|
||||
|
||||
uint8_t mask = 0xff;
|
||||
if(const auto tag = execution_support.memory.tags.find(address); tag != execution_support.memory.tags.end()) {
|
||||
switch(tag->second) {
|
||||
default: break;
|
||||
case Memory::Tag::FlagsH: mask = flags_mask >> 8; break;
|
||||
case Memory::Tag::FlagsL: mask = flags_mask & 0xff; break;
|
||||
}
|
||||
if((mask_position != 1) && value == [ram[1] intValue]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Consider whether this apparent mismatch might be because flags have been written to memory;
|
||||
// allow only one use of the [16-bit] mask per test.
|
||||
bool matched_with_mask = false;
|
||||
while(mask_position < 2) {
|
||||
const uint8_t mask = mask_position ? (flags_mask >> 8) : (flags_mask & 0xff);
|
||||
++mask_position;
|
||||
if((value & mask) == ([ram[1] intValue] & mask)) {
|
||||
matched_with_mask = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(matched_with_mask) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if((execution_support.memory.memory[address] & mask) != ([ram[1] intValue] & mask)) {
|
||||
ramEqual = false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
[self populate:intended_registers status:intended_status value:final_state[@"regs"]];
|
||||
@@ -663,7 +711,15 @@ struct FailedExecution {
|
||||
}
|
||||
|
||||
// Lock in current failure rate.
|
||||
XCTAssertLessThanOrEqual(execution_failures.size(), 1654);
|
||||
XCTAssertLessThanOrEqual(execution_failures.size(), 4138);
|
||||
|
||||
// Current accepted failures:
|
||||
// * 65 instances of DAA with invalid BCD input, and 64 of DAS;
|
||||
// * 2484 instances of LEA from a register, which officially has undefined results;
|
||||
// * 42 instances of AAM 00h for which I can't figure out what to do with flags; and
|
||||
// * 1486 instances of IDIV, most either with a rep or repne that on the 8086 specifically negatives the result,
|
||||
// but some admittedly still unexplained (primarily setting overflow even though the result doesn't overflow;
|
||||
// a couple of online 8086 emulators also didn't throw so maybe this is an 8086 quirk?)
|
||||
|
||||
for(const auto &failure: execution_failures) {
|
||||
NSLog(@"Failed %s — %s", failure.test_name.c_str(), failure.reason.c_str());
|
||||
|
Reference in New Issue
Block a user