1
0
mirror of https://github.com/TomHarte/CLK.git synced 2025-08-08 14:25:05 +00:00

Compare commits

...

51 Commits

Author SHA1 Message Date
Thomas Harte
54ff2fa01f Fix new LES/etc failures.
Remaining: 72.
2025-08-08 09:55:31 -04:00
Thomas Harte
03c70b49ba Throw GPF for overlong instructions; fix BOUND validation.
79 failures outstanding.
2025-08-08 09:43:16 -04:00
Thomas Harte
4b2d8e13d1 Add consts, TODO. 2025-08-08 07:39:34 -04:00
Thomas Harte
a0c50f0521 Support 286-style DAS.
321 failures to go.
2025-08-07 19:49:41 -04:00
Thomas Harte
b15a865c88 Add extra MOV sanity check.
Failures still standing: 406.
2025-08-07 17:40:13 -04:00
Thomas Harte
8e5bbbbc71 Implement 80286 INS/OUTS oddities.
795 failures outstanding.
2025-08-07 15:31:07 -04:00
Thomas Harte
615ebaf711 Correct RCL overflow when shift count is 0.
1,013 failures remaining.
2025-08-07 15:23:11 -04:00
Thomas Harte
0882d2b7ce Correct LEAVE authorisation.
Failures: 1,207.
2025-08-07 15:16:12 -04:00
Thomas Harte
900195efac Correct HLT IP comparison.
Failures: 1,425.
2025-08-07 15:01:22 -04:00
Thomas Harte
b58b962ccf Apply 80286 LODS craziness.
2,425 errors remaining.
2025-08-07 14:53:16 -04:00
Thomas Harte
5255499445 Implement 286 weirdness for SCAS.
2,690 failures.
2025-08-07 14:50:59 -04:00
Thomas Harte
d9a2be4250 Avoid upfront testing for POPA.
Failures: 2,966.
2025-08-07 14:41:01 -04:00
Thomas Harte
256e14a8a6 Decline upfront validation for PUSHA.
Total failures remaining: 3,239.
2025-08-07 14:36:11 -04:00
Thomas Harte
1ab26d4a2f Determine 80286 CMPS rules.
Remaining: 3,521 failures.
2025-08-07 12:28:50 -04:00
Thomas Harte
91b2c751af Determine 80286 logic for MOVS.
4,043 failures left.
2025-08-07 12:17:52 -04:00
Thomas Harte
edf7617d1e Fix is_address.
Failures: 4,568.
2025-08-07 09:25:23 -04:00
Thomas Harte
32666d304f Filter out some illegal JMP/CALL fars.
Failure count now: 5,966.
2025-08-07 09:18:23 -04:00
Thomas Harte
b65f7b4a6a Restrict BOUNDS checks to 80286. 2025-08-06 22:32:50 -04:00
Thomas Harte
7c4df23c1c Fix BOUND.
7085 remaining failures.
2025-08-06 22:19:35 -04:00
Thomas Harte
a8e60163e1 Commute Overflow from fault to trap.
9,331 failures remaining.
2025-08-06 21:30:08 -04:00
Thomas Harte
02ec1b5da6 Fix SHR overflow flag.
Failing: 11,802.
2025-08-06 21:14:15 -04:00
Thomas Harte
a9a6aba862 Correct RCR overflow.
Now: 14,097 failures.
2025-08-06 17:46:05 -04:00
Thomas Harte
03c6a60f68 Avoid extra judgment on LEAVE.
Failures remaining: 16,295.
2025-08-06 17:19:20 -04:00
Thomas Harte
8ab688687e Decode .6 as SAL.
Amazingly: now 20,814 failures outstanding.
2025-08-06 16:30:55 -04:00
Thomas Harte
bdec32722e Include failures/file. 2025-08-06 16:07:54 -04:00
Thomas Harte
ad50e5c754 Ensure an invalid instruction is generated upon length limit. 2025-08-06 15:59:24 -04:00
Thomas Harte
9c48e44e9e Fix fast-path selection.
50,814 failures.
2025-08-06 15:33:51 -04:00
Thomas Harte
76284eb462 Fix 8088 assumption about unused flags; 80286 PUSHF now passes amongst others.
51,091 failures still to fix though.
2025-08-06 15:31:03 -04:00
Thomas Harte
0745c5128a Avoid expensive path for 8088; pull out allow list. 2025-08-06 15:21:54 -04:00
Thomas Harte
01fbe2d3de Support 808286 STOS oddities. 2025-08-06 13:37:34 -04:00
Thomas Harte
9e14c22259 Take another run at ENTER. 2025-08-06 12:55:37 -04:00
Thomas Harte
dff0111cd5 Overtly capture decoding failures. 2025-08-05 13:03:54 -04:00
Thomas Harte
e7452b0ea1 Continue accepting F7.2 as TEST. 2025-08-04 21:45:14 -04:00
Thomas Harte
61a0f892c4 Fix PUSH immediate. 2025-08-04 21:23:27 -04:00
Thomas Harte
4ceab01ed4 Fix result of IMUL_3. 2025-08-04 21:05:14 -04:00
Thomas Harte
9908969eea Diagnose, correct AAA and AAS. 2025-08-04 17:49:07 -04:00
Thomas Harte
19a78ef1ac Correct for 286+ PUSH SP. 2025-08-04 17:23:02 -04:00
Thomas Harte
4785c1ae84 Grab new punchlist. 2025-08-04 17:19:11 -04:00
Thomas Harte
ef03841efa Deal with potential reason for wrong top-of-flags. 2025-08-04 17:14:16 -04:00
Thomas Harte
4747a70ce7 Correct for accesses right at segment end. 2025-08-04 17:08:01 -04:00
Thomas Harte
cd986cc2dc Ensure tests get the default IDT. 2025-08-04 12:47:52 -04:00
Thomas Harte
c29d5ca4a8 Catch address wraparound out-of-bounds access. 2025-08-04 09:32:35 -04:00
Thomas Harte
56b49011d6 Shorten reports. 2025-08-04 09:21:49 -04:00
Thomas Harte
48c55211e6 Fix descriptor bounds test. 2025-08-04 09:16:33 -04:00
Thomas Harte
72f68f3b0b Include hash in error record. 2025-08-03 20:11:35 -04:00
Thomas Harte
7b6dddc994 Include number. 2025-08-03 17:57:26 -04:00
Thomas Harte
51fbe4e8c5 Consume 286 HLT. 2025-08-03 17:41:02 -04:00
Thomas Harte
c148d9ee6c Ensure ENTER can execute. 2025-08-03 17:30:02 -04:00
Thomas Harte
9dfe59a104 Take a swing at three-operand IMUL. 2025-08-02 22:23:34 -04:00
Thomas Harte
b6aae65afd Clean up, separate. 2025-08-02 21:45:01 -04:00
Thomas Harte
9fed93a771 Use 286 test suite for decoding tests too. 2025-08-02 21:31:04 -04:00
20 changed files with 609 additions and 233 deletions

View File

@@ -196,6 +196,7 @@ std::pair<int, typename Decoder<model>::InstructionT> Decoder<model>::decode(
} else { } else {
immediate(Operation::PUSH, DataSize::Byte); immediate(Operation::PUSH, DataSize::Byte);
operation_size_ = data_size_; operation_size_ = data_size_;
sign_extend_operand_ = true;
} }
break; break;
case 0x6b: case 0x6b:
@@ -736,9 +737,8 @@ std::pair<int, typename Decoder<model>::InstructionT> Decoder<model>::decode(
switch(reg) { switch(reg) {
default: default:
// case 1 is treated as another form of TEST on the 8086. // case 1 is treated as another form of TEST through to at least the 80286.
// (and, I guess, the 80186?) if constexpr (model >= Model::i80386) {
if constexpr (model >= Model::i80286) {
return undefined(); return undefined();
} }
[[fallthrough]]; [[fallthrough]];
@@ -799,13 +799,20 @@ std::pair<int, typename Decoder<model>::InstructionT> Decoder<model>::decode(
switch(reg) { switch(reg) {
default: default:
if constexpr (model == Model::i8086) { switch(model) {
if(source_ == Source::eCX) { case Model::i8086:
set(Operation::SETMOC); if(source_ == Source::eCX) {
} else { set(Operation::SETMOC);
set(Operation::SETMO); } else {
} set(Operation::SETMO);
} else { }
break;
case Model::i80286:
set(Operation::SAL);
break;
default:
return undefined(); return undefined();
} }
break; break;
@@ -865,6 +872,13 @@ std::pair<int, typename Decoder<model>::InstructionT> Decoder<model>::decode(
source_ = Source::Immediate; source_ = Source::Immediate;
destination_ = memreg; destination_ = memreg;
operand_size_ = operation_size_; operand_size_ = operation_size_;
// This form requires that the reg field be blank
if constexpr (model >= Model::i80286) {
if(reg != 0) {
return undefined();
}
}
break; break;
case ModRegRMFormat::MemRegADD_to_CMP: case ModRegRMFormat::MemRegADD_to_CMP:
@@ -1017,8 +1031,13 @@ std::pair<int, typename Decoder<model>::InstructionT> Decoder<model>::decode(
} }
} }
} else { } else {
// Provide a genuine measure of further bytes required. // Provide a genuine measure of further bytes required, or post a bad instruction
return std::make_pair(-(outstanding_bytes - bytes_to_consume), InstructionT()); // if the length limit has been breached.
if(consumed_ != max_instruction_length) {
return std::make_pair(-(outstanding_bytes - bytes_to_consume), InstructionT());
} else {
return overlong();
}
} }
} }
@@ -1037,6 +1056,18 @@ std::pair<int, typename Decoder<model>::InstructionT> Decoder<model>::decode(
// operations would unlock an extra bit of storage for a net gain of 239 extra operation types and thereby // 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. // alleviating any concerns over whether there'll be space to handle MMX, floating point extensions, etc.
if constexpr (model >= Model::i80286) {
if(operation_ == Operation::BOUND && !is_address(source_)) {
return undefined();
}
}
if(
(operation_ == Operation::JMPfar || operation_ == Operation::CALLfar) &&
destination_ < Source::DirectAddress
) {
return undefined();
}
const auto result = std::make_pair( const auto result = std::make_pair(
consumed_, consumed_,
InstructionT( InstructionT(
@@ -1058,14 +1089,7 @@ std::pair<int, typename Decoder<model>::InstructionT> Decoder<model>::decode(
// Check for a too-long instruction. // Check for a too-long instruction.
if(consumed_ == max_instruction_length) { if(consumed_ == max_instruction_length) {
std::pair<int, InstructionT> result; return overlong();
if(max_instruction_length == 65536) {
result = std::make_pair(consumed_, InstructionT(Operation::NOP));
} else {
result = std::make_pair(consumed_, InstructionT());
}
reset_parsing();
return result;
} }
// i.e. not done yet. // i.e. not done yet.

View File

@@ -349,6 +349,7 @@ private:
phase_ = Phase::DisplacementOrOperand; phase_ = Phase::DisplacementOrOperand;
displacement_size_ = DataSize::Word; displacement_size_ = DataSize::Word;
operand_size_ = DataSize::Byte; operand_size_ = DataSize::Byte;
operation_size_ = DataSize::Byte;
} }
/// Sets up the operation size, oncoming phase and modregrm format for a member of the shift group (i.e. 'group 2'). /// Sets up the operation size, oncoming phase and modregrm format for a member of the shift group (i.e. 'group 2').
@@ -367,6 +368,16 @@ private:
reset_parsing(); reset_parsing();
return result; return result;
} }
std::pair<int, typename Decoder<model>::InstructionT> overlong() {
const auto consumed = consumed_;
reset_parsing();
if(consumed == 65536) {
return std::make_pair(consumed, InstructionT(Operation::NOP));
} else {
return std::make_pair(consumed, InstructionT(ExceptionCode()));
}
}
}; };
extern template class InstructionSet::x86::Decoder<InstructionSet::x86::Model::i8086>; extern template class InstructionSet::x86::Decoder<InstructionSet::x86::Model::i8086>;

View File

@@ -81,6 +81,12 @@ struct SegmentDescriptor {
); );
}; };
// Test for bounds; end && end < begin captures instances where end is
// both out of bounds and beyond the range of AddressT.
if(begin < bounds_.begin || end > bounds_.end || (end && end < begin)) {
throw_exception();
}
// Tested at loading (?): present(), privilege_level(). // Tested at loading (?): present(), privilege_level().
if(type == AccessType::Read && executable() && !readable()) { if(type == AccessType::Read && executable() && !readable()) {
@@ -90,10 +96,6 @@ struct SegmentDescriptor {
if(type == AccessType::Write && !executable() && !writeable()) { if(type == AccessType::Write && !executable() && !writeable()) {
throw_exception(); throw_exception();
} }
if(begin < bounds_.begin || end >= bounds_.end) {
throw_exception();
}
} }
void validate_as(const Source segment) const { void validate_as(const Source segment) const {

View File

@@ -8,6 +8,9 @@
#pragma once #pragma once
#include <cassert>
#include <cstdint>
namespace InstructionSet::x86 { namespace InstructionSet::x86 {
enum class Vector: uint8_t { enum class Vector: uint8_t {
@@ -82,7 +85,7 @@ constexpr bool posts_next_ip(const Vector vector) {
case SingleStep: case SingleStep:
case Breakpoint: case Breakpoint:
case Overflow: case Overflow:
return false; return true;
} }
} }
@@ -108,6 +111,9 @@ struct ExceptionCode {
// 1 => trigger was external to program code; // 1 => trigger was external to program code;
// 0 => trigger was caused by the instruction described by the CS:IP that is on the stack. // 0 => trigger was caused by the instruction described by the CS:IP that is on the stack.
ExceptionCode(const uint16_t value) :
value_(value) {}
operator uint16_t() const { operator uint16_t() const {
return value_; return value_;
} }

View File

@@ -81,6 +81,18 @@ class Flags {
public: public:
using FlagT = uint32_t; using FlagT = uint32_t;
Flags(const Model model) {
switch(model) {
case Model::i8086:
case Model::i80186:
forced_set_ = 0xf002;
break;
default:
forced_set_ = 0x0002;
break;
}
}
// Flag getters. // Flag getters.
template <Flag flag_v> bool flag() const { template <Flag flag_v> bool flag() const {
switch(flag_v) { switch(flag_v) {
@@ -177,7 +189,7 @@ public:
uint16_t get() const { uint16_t get() const {
return return
0xf002 | forced_set_ |
(flag<Flag::Carry>() ? FlagValue::Carry : 0) | (flag<Flag::Carry>() ? FlagValue::Carry : 0) |
(flag<Flag::AuxiliaryCarry>() ? FlagValue::AuxiliaryCarry : 0) | (flag<Flag::AuxiliaryCarry>() ? FlagValue::AuxiliaryCarry : 0) |
@@ -232,6 +244,9 @@ private:
// Odd number of bits => set; even => unset. // Odd number of bits => set; even => unset.
uint32_t parity_{}; uint32_t parity_{};
// Model specific stuff: bits that are always set, regardless of other state.
uint16_t forced_set_{};
}; };
} }

View File

@@ -128,7 +128,7 @@ void mul(
} }
template <typename IntT, typename ContextT> template <typename IntT, typename ContextT>
void imul( void imul_double(
modify_t<IntT> destination_high, modify_t<IntT> destination_high,
modify_t<IntT> destination_low, modify_t<IntT> destination_low,
read_t<IntT> source, read_t<IntT> source,
@@ -167,6 +167,22 @@ void imul(
context.flags.template set_from<Flag::Overflow, Flag::Carry>(destination_high != sign_extension); context.flags.template set_from<Flag::Overflow, Flag::Carry>(destination_high != sign_extension);
} }
template <typename IntT, typename ContextT>
void imul_single(
write_t<IntT> destination,
read_t<IntT> source1,
read_t<IntT> source2,
ContextT &context
) {
using sIntT = typename std::make_signed<IntT>::type;
const auto top_part = IntT((sIntT(source1) * sIntT(source2)) >> (8 * sizeof(IntT)));
const auto result = IntT(sIntT(source1) * sIntT(source2));
destination = result;
const auto sign_extension = (result & Numeric::top_bit<IntT>()) ? IntT(~0) : 0;
context.flags.template set_from<Flag::Overflow, Flag::Carry>(top_part != sign_extension);
}
template <typename ContextT> template <typename ContextT>
void divide_error(ContextT &context) { void divide_error(ContextT &context) {
// 8086-style: just segue directly to the interrupt. // 8086-style: just segue directly to the interrupt.

View File

@@ -22,10 +22,18 @@ void aaas(
) { ) {
if((ax.halves.low & 0x0f) > 9 || context.flags.template flag<Flag::AuxiliaryCarry>()) { if((ax.halves.low & 0x0f) > 9 || context.flags.template flag<Flag::AuxiliaryCarry>()) {
if constexpr (add) { if constexpr (add) {
ax.halves.low += 6; if constexpr (ContextT::model <= Model::i80186) {
ax.halves.low += 6;
} else {
ax.full += 6;
}
++ax.halves.high; ++ax.halves.high;
} else { } else {
ax.halves.low -= 6; if constexpr (ContextT::model <= Model::i80186) {
ax.halves.low -= 6;
} else {
ax.full -= 6;
}
--ax.halves.high; --ax.halves.high;
} }
context.flags.template set_from<Flag::Carry, Flag::AuxiliaryCarry>(1); context.flags.template set_from<Flag::Carry, Flag::AuxiliaryCarry>(1);
@@ -96,18 +104,27 @@ void daas(
ContextT &context ContextT &context
) { ) {
bool top_exceeded_threshold; bool top_exceeded_threshold;
if constexpr (ContextT::model == Model::i8086) { constexpr bool is_8086 = ContextT::model == Model::i8086;
if constexpr (is_8086) {
top_exceeded_threshold = al > (context.flags.template flag<Flag::AuxiliaryCarry>() ? 0x9f : 0x99); top_exceeded_threshold = al > (context.flags.template flag<Flag::AuxiliaryCarry>() ? 0x9f : 0x99);
} else { } else {
top_exceeded_threshold = al > 0x99; top_exceeded_threshold = al > 0x99;
} }
const auto initial_cf = context.flags.template flag<Flag::Carry>();
if((al & 0x0f) > 0x09 || context.flags.template flag<Flag::AuxiliaryCarry>()) { if((al & 0x0f) > 0x09 || context.flags.template flag<Flag::AuxiliaryCarry>()) {
if constexpr (add) al += 0x06; else al -= 0x06; const auto prior_al = al;
if constexpr (add) {
al += 0x06;
if(!is_8086 && al < prior_al) context.flags.template set_from<Flag::Carry>(1);
} else {
al -= 0x06;
if(!is_8086 && al > prior_al) context.flags.template set_from<Flag::Carry>(1);
}
context.flags.template set_from<Flag::AuxiliaryCarry>(1); context.flags.template set_from<Flag::AuxiliaryCarry>(1);
} }
if(top_exceeded_threshold || context.flags.template flag<Flag::Carry>()) { if(top_exceeded_threshold || initial_cf) {
if constexpr (add) al += 0x60; else al -= 0x60; if constexpr (add) al += 0x60; else al -= 0x60;
context.flags.template set_from<Flag::Carry>(1); context.flags.template set_from<Flag::Carry>(1);
} }

View File

@@ -263,18 +263,17 @@ void into(
template <typename IntT, typename AddressT, typename InstructionT, typename ContextT> template <typename IntT, typename AddressT, typename InstructionT, typename ContextT>
void bound( void bound(
const InstructionT &instruction, const InstructionT &instruction,
read_t<AddressT> destination, read_t<IntT> destination,
read_t<AddressT> source, read_t<AddressT> source,
ContextT &context ContextT &context
) { ) {
using sIntT = typename std::make_signed<IntT>::type; using sIntT = typename std::make_signed<IntT>::type;
const auto source_segment = instruction.data_segment(); const auto source_segment = instruction.data_segment();
context.memory.preauthorise_read(source_segment, source, 2*sizeof(IntT));
const auto lower_bound = const auto lower_bound =
sIntT(context.memory.template access<IntT, AccessType::PreauthorisedRead>(source_segment, source)); sIntT(context.memory.template access<IntT, AccessType::Read>(source_segment, source));
const auto upper_bound = const auto upper_bound =
sIntT(context.memory.template access<IntT, AccessType::PreauthorisedRead>(source_segment, IntT(source + 2))); sIntT(context.memory.template access<IntT, AccessType::Read>(source_segment, IntT(source + 2)));
if(sIntT(destination) < lower_bound || sIntT(destination) > upper_bound) { if(sIntT(destination) < lower_bound || sIntT(destination) > upper_bound) {
constexpr auto exception = Exception::exception<Vector::BoundRangeExceeded>(); constexpr auto exception = Exception::exception<Vector::BoundRangeExceeded>();

View File

@@ -40,12 +40,11 @@ void ld(
uint16_t source_address = uint16_t(address<uint16_t, AccessType::Read>(instruction, pointer, context)); uint16_t source_address = uint16_t(address<uint16_t, AccessType::Read>(instruction, pointer, context));
const Source source_segment = instruction.data_segment(); const Source source_segment = instruction.data_segment();
context.memory.preauthorise_read(source_segment, source_address, 4);
const auto offset = const auto offset =
context.memory.template access<uint16_t, AccessType::PreauthorisedRead>(source_segment, source_address); context.memory.template access<uint16_t, AccessType::Read>(source_segment, source_address);
source_address += 2; source_address += 2;
const auto segment = const auto segment =
context.memory.template access<uint16_t, AccessType::PreauthorisedRead>(source_segment, source_address); context.memory.template access<uint16_t, AccessType::Read>(source_segment, source_address);
context.segments.preauthorise(selector, segment); context.segments.preauthorise(selector, segment);
destination = offset; destination = offset;

View File

@@ -128,6 +128,12 @@ template <
); );
}; };
const auto source_address = [&]() -> AddressT {
return AddressT(
address<AddressT, AccessType::Read>(instruction, instruction.source(), context)
);
};
// Some instructions use a pair of registers as an extended accumulator — DX:AX or EDX:EAX. // 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, // 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. // i.e. AX split into high and low parts.
@@ -190,8 +196,14 @@ template <
case Operation::NOP: return; case Operation::NOP: return;
case Operation::Invalid: case Operation::Invalid:
// TODO: ask whether the issue was overlong. If so then
// get an exception code and do a GPF.
if constexpr (!uses_8086_exceptions(ContextT::model)) { if constexpr (!uses_8086_exceptions(ContextT::model)) {
throw Exception::exception<Vector::InvalidOpcode>(); if(instruction.invalid_is_gpf()) {
throw Exception::exception<Vector::GeneralProtectionFault>(instruction.gpf_exception_code());
} else {
throw Exception::exception<Vector::InvalidOpcode>();
}
} }
return; return;
@@ -241,18 +253,21 @@ template <
return; return;
case Operation::MUL: Primitive::mul<IntT>(pair_high(), pair_low(), source_r(), context); return; case Operation::MUL: Primitive::mul<IntT>(pair_high(), pair_low(), source_r(), context); return;
case Operation::IMUL_1: Primitive::imul<IntT>(pair_high(), pair_low(), source_r(), context); return; case Operation::IMUL_1:
Primitive::imul_double<IntT>(pair_high(), pair_low(), source_r(), context);
return;
case Operation::IMUL_3:
Primitive::imul_single<IntT>(destination_w(), source_r(), IntT(instruction.operand()), context);
return;
case Operation::DIV: Primitive::div<IntT>(pair_high(), pair_low(), source_r(), context); return; case Operation::DIV: Primitive::div<IntT>(pair_high(), pair_low(), source_r(), context); return;
case Operation::IDIV: Primitive::idiv<false, IntT>(pair_high(), pair_low(), source_r(), context); return; case Operation::IDIV: Primitive::idiv<false, IntT>(pair_high(), pair_low(), source_r(), context); return;
case Operation::IDIV_REP: case Operation::IDIV_REP:
if constexpr (ContextT::model == Model::i8086) { if constexpr (ContextT::model < Model::i80286) {
Primitive::idiv<true, IntT>(pair_high(), pair_low(), source_r(), context); Primitive::idiv<true, IntT>(pair_high(), pair_low(), source_r(), context);
break; break;
} else { } else {
static_assert(int(Operation::IDIV_REP) == int(Operation::LEAVE)); static_assert(int(Operation::IDIV_REP) == int(Operation::LEAVE));
if constexpr (std::is_same_v<IntT, uint16_t> || std::is_same_v<IntT, uint32_t>) { Primitive::leave<IntT>(context);
Primitive::leave<IntT>(context);
}
} }
return; return;
@@ -431,7 +446,7 @@ template <
break; break;
} else { } else {
static_assert(int(Operation::SETMOC) == int(Operation::BOUND)); static_assert(int(Operation::SETMOC) == int(Operation::BOUND));
Primitive::bound<IntT, AddressT>(instruction, destination_r(), source_r(), context); Primitive::bound<IntT, AddressT>(instruction, destination_r(), source_address(), context);
} }
return; return;
@@ -453,9 +468,14 @@ template <
} }
} break; } break;
case Operation::PUSH: case Operation::PUSH:
Primitive::push<IntT, false>(source_rmw(), context); // PUSH SP modifies SP before pushing it; if constexpr (ContextT::model >= Model::i80286) {
// hence PUSH is sometimes read-modify-write. Primitive::push<IntT, false>(source_r(), context);
break; } else {
Primitive::push<IntT, false>(source_rmw(), context); // Prior to the 286, PUSH SP modifies SP
// before pushing it; hence PUSH is
// sometimes read-modify-write.
}
return;
case Operation::POPF: case Operation::POPF:
if constexpr (std::is_same_v<IntT, uint16_t> || std::is_same_v<IntT, uint32_t>) { if constexpr (std::is_same_v<IntT, uint16_t> || std::is_same_v<IntT, uint32_t>) {
@@ -570,7 +590,6 @@ template <
// LSL // LSL
// LTR // LTR
// STR // STR
// IMUL_3
// LOADALL // LOADALL
} }
@@ -592,7 +611,7 @@ void perform(
const Instruction<type> &instruction, const Instruction<type> &instruction,
ContextT &context ContextT &context
) { ) {
const auto size = [](DataSize operation_size, AddressSize address_size) constexpr -> int { const auto size = [](const DataSize operation_size, const AddressSize address_size) constexpr -> int {
return int(operation_size) + (int(address_size) << 2); return int(operation_size) + (int(address_size) << 2);
}; };
@@ -647,7 +666,8 @@ void perform(
// This is reachable only if the data and address size combination in use isn't available // This is reachable only if the data and address size combination in use isn't available
// on the processor model nominated. // on the processor model nominated.
assert(false);} assert(false);
}
// //
// Public function; just indirects into a trampoline into a version of perform templated on data and address size. // Public function; just indirects into a trampoline into a version of perform templated on data and address size.
@@ -678,6 +698,7 @@ void perform(
); );
return; return;
} catch (const InstructionSet::x86::Exception exception) { } catch (const InstructionSet::x86::Exception exception) {
context.flow_controller.cancel_repetition();
fault(exception, context, source_ip); fault(exception, context, source_ip);
} }
} }

View File

@@ -51,8 +51,28 @@ void cmps(
return; return;
} }
IntT lhs = context.memory.template access<IntT, AccessType::Read>(instruction.data_segment(), eSI); IntT lhs, rhs;
const IntT rhs = context.memory.template access<IntT, AccessType::Read>(Source::ES, eDI); if constexpr (!uses_8086_exceptions(ContextT::model)) {
try {
rhs = context.memory.template access<IntT, AccessType::Read>(Source::ES, eDI);
} catch (const Exception &e) {
eDI += context.flags.template direction<AddressT>() * sizeof(IntT);
throw e;
}
try {
lhs = context.memory.template access<IntT, AccessType::Read>(instruction.data_segment(), eSI);
} catch (const Exception &e) {
eDI += context.flags.template direction<AddressT>() * sizeof(IntT);
eSI += context.flags.template direction<AddressT>() * sizeof(IntT);
repeat<AddressT, repetition>(eCX, context);
throw e;
}
} else {
rhs = context.memory.template access<IntT, AccessType::Read>(Source::ES, eDI);
lhs = context.memory.template access<IntT, AccessType::Read>(instruction.data_segment(), eSI);
}
eSI += context.flags.template direction<AddressT>() * sizeof(IntT); eSI += context.flags.template direction<AddressT>() * sizeof(IntT);
eDI += context.flags.template direction<AddressT>() * sizeof(IntT); eDI += context.flags.template direction<AddressT>() * sizeof(IntT);
@@ -72,11 +92,22 @@ void scas(
return; return;
} }
const IntT rhs = context.memory.template access<IntT, AccessType::Read>(Source::ES, eDI); IntT rhs;
if(!uses_8086_exceptions(ContextT::model)) {
try {
rhs = context.memory.template access<IntT, AccessType::Read>(Source::ES, eDI);
} catch (const Exception &e) {
eDI += context.flags.template direction<AddressT>() * sizeof(IntT);
repeat<AddressT, repetition>(eCX, context);
throw e;
}
} else {
rhs = context.memory.template access<IntT, AccessType::Read>(Source::ES, eDI);
}
eDI += context.flags.template direction<AddressT>() * sizeof(IntT); eDI += context.flags.template direction<AddressT>() * sizeof(IntT);
Primitive::sub<false, AccessType::Read, IntT>(eAX, rhs, context); Primitive::sub<false, AccessType::Read, IntT>(eAX, rhs, context);
repeat<AddressT, repetition>(eCX, context); repeat<AddressT, repetition>(eCX, context);
} }
@@ -92,9 +123,19 @@ void lods(
return; return;
} }
eAX = context.memory.template access<IntT, AccessType::Read>(instruction.data_segment(), eSI); if(!uses_8086_exceptions(ContextT::model)) {
eSI += context.flags.template direction<AddressT>() * sizeof(IntT); try {
eAX = context.memory.template access<IntT, AccessType::Read>(instruction.data_segment(), eSI);
} catch (const Exception &e) {
eSI += context.flags.template direction<AddressT>() * sizeof(IntT);
repeat<AddressT, repetition>(eCX, context);
throw e;
}
} else {
eAX = context.memory.template access<IntT, AccessType::Read>(instruction.data_segment(), eSI);
}
eSI += context.flags.template direction<AddressT>() * sizeof(IntT);
repeat<AddressT, repetition>(eCX, context); repeat<AddressT, repetition>(eCX, context);
} }
@@ -110,12 +151,33 @@ void movs(
return; return;
} }
context.memory.template access<IntT, AccessType::Write>(Source::ES, eDI) = if constexpr (!uses_8086_exceptions(ContextT::model)) {
context.memory.template access<IntT, AccessType::Read>(instruction.data_segment(), eSI); IntT temp;
try {
temp = context.memory.template access<IntT, AccessType::Read>(instruction.data_segment(), eSI);
} catch (const Exception &e) {
eSI += context.flags.template direction<AddressT>() * sizeof(IntT);
repeat<AddressT, repetition>(eCX, context);
throw e;
}
try {
context.memory.template access<IntT, AccessType::Write>(Source::ES, eDI) = temp;
} catch (const Exception &e) {
eSI += context.flags.template direction<AddressT>() * sizeof(IntT);
eDI += context.flags.template direction<AddressT>() * sizeof(IntT);
repeat<AddressT, repetition>(eCX, context);
repeat<AddressT, repetition>(eCX, context);
throw e;
}
} else {
context.memory.template access<IntT, AccessType::Write>(Source::ES, eDI) =
context.memory.template access<IntT, AccessType::Read>(instruction.data_segment(), eSI);
}
eSI += context.flags.template direction<AddressT>() * sizeof(IntT); eSI += context.flags.template direction<AddressT>() * sizeof(IntT);
eDI += context.flags.template direction<AddressT>() * sizeof(IntT); eDI += context.flags.template direction<AddressT>() * sizeof(IntT);
repeat<AddressT, repetition>(eCX, context); repeat<AddressT, repetition>(eCX, context);
} }
@@ -123,16 +185,33 @@ template <typename IntT, typename AddressT, Repetition repetition, typename Cont
void stos( void stos(
AddressT &eCX, AddressT &eCX,
AddressT &eDI, AddressT &eDI,
IntT &eAX, const IntT eAX,
ContextT &context ContextT &context
) { ) {
if(repetition_over<AddressT, repetition>(eCX)) { if(repetition_over<AddressT, repetition>(eCX)) {
return; return;
} }
context.memory.template access<IntT, AccessType::Write>(Source::ES, eDI) = eAX; if constexpr (!uses_8086_exceptions(ContextT::model)) {
eDI += context.flags.template direction<AddressT>() * sizeof(IntT); try {
context.memory.template access<IntT, AccessType::Write>(Source::ES, eDI) = eAX;
} catch (const Exception &e) {
// Empirical quirk of at least the 286: DI is adjusted even if the following access throws,
// and CX has been adjusted... twice?
//
// (yes: including even if CX has already hit zero)
if constexpr (ContextT::model == Model::i80286) {
eDI += context.flags.template direction<AddressT>() * sizeof(IntT);
repeat<AddressT, repetition>(eCX, context);
repeat<AddressT, repetition>(eCX, context);
}
throw e;
}
} else {
context.memory.template access<IntT, AccessType::Write>(Source::ES, eDI) = eAX;
}
eDI += context.flags.template direction<AddressT>() * sizeof(IntT);
repeat<AddressT, repetition>(eCX, context); repeat<AddressT, repetition>(eCX, context);
} }
@@ -148,12 +227,26 @@ void outs(
return; return;
} }
context.io.template out<IntT>( // TODO: break down as two potentially-throwing steps.
port, if(!uses_8086_exceptions(ContextT::model)) {
context.memory.template access<IntT, AccessType::Read>(instruction.data_segment(), eSI) try {
); context.io.template out<IntT>(
eSI += context.flags.template direction<AddressT>() * sizeof(IntT); port,
context.memory.template access<IntT, AccessType::Read>(instruction.data_segment(), eSI)
);
} catch (const Exception &e) {
eSI += context.flags.template direction<AddressT>() * sizeof(IntT);
repeat<AddressT, repetition>(eCX, context);
throw e;
}
} else {
context.io.template out<IntT>(
port,
context.memory.template access<IntT, AccessType::Read>(instruction.data_segment(), eSI)
);
}
eSI += context.flags.template direction<AddressT>() * sizeof(IntT);
repeat<AddressT, repetition>(eCX, context); repeat<AddressT, repetition>(eCX, context);
} }
@@ -168,9 +261,22 @@ void ins(
return; return;
} }
context.memory.template access<IntT, AccessType::Write>(Source::ES, eDI) = context.io.template in<IntT>(port); // TODO: break down as two potentially-throwing steps.
eDI += context.flags.template direction<AddressT>() * sizeof(IntT); if(!uses_8086_exceptions(ContextT::model)) {
try {
context.memory.template access<IntT, AccessType::Write>(Source::ES, eDI) =
context.io.template in<IntT>(port);
} catch (const Exception &e) {
eDI += context.flags.template direction<AddressT>() * sizeof(IntT);
repeat<AddressT, repetition>(eCX, context);
repeat<AddressT, repetition>(eCX, context);
throw e;
}
} else {
context.memory.template access<IntT, AccessType::Write>(Source::ES, eDI) = context.io.template in<IntT>(port);
}
eDI += context.flags.template direction<AddressT>() * sizeof(IntT);
repeat<AddressT, repetition>(eCX, context); repeat<AddressT, repetition>(eCX, context);
} }

View File

@@ -50,12 +50,18 @@ uint32_t address(
if constexpr (instruction_type(ContextT::model) != InstructionType::Bits16) { if constexpr (instruction_type(ContextT::model) != InstructionType::Bits16) {
address <<= pointer.scale(); address <<= pointer.scale();
} }
// printf("%d + %d", address, instruction.offset());
address += instruction.offset(); address += instruction.offset();
if constexpr (source == Source::IndirectNoBase) { if constexpr (source == Source::IndirectNoBase) {
// printf("\n");
return address; return address;
} }
return address + resolve<uint16_t, AccessType::Read>(instruction, pointer.base(), pointer, context);
const auto base = resolve<uint16_t, AccessType::Read>(instruction, pointer.base(), pointer, context);
// printf("+ %d\n", base);
address += base;
return address;
} }
/// @returns a pointer to the contents of the register identified by the combination of @c IntT and @c Source if any; /// @returns a pointer to the contents of the register identified by the combination of @c IntT and @c Source if any;
@@ -207,6 +213,10 @@ typename Accessor<IntT, access>::type resolve(
// Do it and exit. // Do it and exit.
// //
// TODO: support 32-bit addresses. // TODO: support 32-bit addresses.
//
// TODO: validate offset. If it's greater than the address size, fault?
//
return context.memory.template access<IntT, access>(instruction.data_segment(), uint16_t(target_address)); return context.memory.template access<IntT, access>(instruction.data_segment(), uint16_t(target_address));
} }

View File

@@ -52,7 +52,9 @@ void rcl(
const auto temp_count = count % (Numeric::bit_size<IntT>() + 1); const auto temp_count = count % (Numeric::bit_size<IntT>() + 1);
auto carry = context.flags.template carry_bit<IntT>(); auto carry = context.flags.template carry_bit<IntT>();
switch(temp_count) { switch(temp_count) {
case 0: break; case 0:
if(!count) return;
break;
case Numeric::bit_size<IntT>(): { case Numeric::bit_size<IntT>(): {
const IntT temp_carry = destination & 1; const IntT temp_carry = destination & 1;
destination = IntT((destination >> 1) | (carry << (Numeric::bit_size<IntT>() - 1))); destination = IntT((destination >> 1) | (carry << (Numeric::bit_size<IntT>() - 1)));
@@ -69,10 +71,12 @@ void rcl(
} break; } break;
} }
context.flags.template set_from<Flag::Carry>(carry); // Intention: overflow is set if the last shift step affected the top bit of
// the destination.
context.flags.template set_from<Flag::Overflow>( context.flags.template set_from<Flag::Overflow>(
((destination >> (Numeric::bit_size<IntT>() - 1)) & 1) ^ carry ((destination >> (Numeric::bit_size<IntT>() - 1)) & 1) ^ carry
); );
context.flags.template set_from<Flag::Carry>(carry);
} }
template <typename IntT, typename ContextT> template <typename IntT, typename ContextT>
@@ -96,13 +100,12 @@ void rcr(
OD; OD;
*/ */
auto carry = context.flags.template carry_bit<IntT>(); auto carry = context.flags.template carry_bit<IntT>();
context.flags.template set_from<Flag::Overflow>(
((destination >> (Numeric::bit_size<IntT>() - 1)) & 1) ^ carry
);
const auto temp_count = count % (Numeric::bit_size<IntT>() + 1); const auto temp_count = count % (Numeric::bit_size<IntT>() + 1);
switch(temp_count) { switch(temp_count) {
case 0: break; case 0:
if(!count) return;
break;
case Numeric::bit_size<IntT>(): { case Numeric::bit_size<IntT>(): {
const IntT temp_carry = destination & Numeric::top_bit<IntT>(); const IntT temp_carry = destination & Numeric::top_bit<IntT>();
destination = IntT((destination << 1) | carry); destination = IntT((destination << 1) | carry);
@@ -119,6 +122,11 @@ void rcr(
} break; } break;
} }
// Intention: overflow is set if the last shift step affected the top bit of
// the destination.
context.flags.template set_from<Flag::Overflow>(
(destination ^ (destination << 1)) & Numeric::top_bit<IntT>()
);
context.flags.template set_from<Flag::Carry>(carry); context.flags.template set_from<Flag::Carry>(carry);
} }
@@ -336,7 +344,7 @@ void shr(
return; return;
} }
context.flags.template set_from<Flag::Overflow>(Numeric::top_bit<IntT>() & destination); context.flags.template set_from<Flag::Overflow>(count == 1 && Numeric::top_bit<IntT>() & destination);
if(count == Numeric::bit_size<IntT>()) { if(count == Numeric::bit_size<IntT>()) {
context.flags.template set_from<Flag::Carry>(Numeric::top_bit<IntT>() & destination); context.flags.template set_from<Flag::Carry>(Numeric::top_bit<IntT>() & destination);
destination = 0; destination = 0;

View File

@@ -94,25 +94,38 @@ template <typename IntT, typename ContextT>
void popa( void popa(
ContextT &context ContextT &context
) { ) {
context.memory.preauthorise_stack_read(sizeof(IntT) * 8); const auto initial_sp = context.registers.sp();
if constexpr (std::is_same_v<IntT, uint32_t>) { const auto do_pops = [&] {
context.registers.edi() = pop<uint32_t, true>(context); if constexpr (std::is_same_v<IntT, uint32_t>) {
context.registers.esi() = pop<uint32_t, true>(context); context.registers.edi() = pop<uint32_t, false>(context);
context.registers.ebp() = pop<uint32_t, true>(context); context.registers.esi() = pop<uint32_t, false>(context);
context.registers.esp() += 4; context.registers.ebp() = pop<uint32_t, false>(context);
context.registers.ebx() = pop<uint32_t, true>(context); context.registers.esp() += 4;
context.registers.edx() = pop<uint32_t, true>(context); context.registers.ebx() = pop<uint32_t, false>(context);
context.registers.ecx() = pop<uint32_t, true>(context); context.registers.edx() = pop<uint32_t, false>(context);
context.registers.eax() = pop<uint32_t, true>(context); context.registers.ecx() = pop<uint32_t, false>(context);
context.registers.eax() = pop<uint32_t, false>(context);
} else {
context.registers.di() = pop<uint16_t, false>(context);
context.registers.si() = pop<uint16_t, false>(context);
context.registers.bp() = pop<uint16_t, false>(context);
context.registers.sp() += 2;
context.registers.bx() = pop<uint16_t, false>(context);
context.registers.dx() = pop<uint16_t, false>(context);
context.registers.cx() = pop<uint16_t, false>(context);
context.registers.ax() = pop<uint16_t, false>(context);
}
};
if(!uses_8086_exceptions(ContextT::model)) {
try {
do_pops();
} catch (const Exception &e) {
context.registers.sp() = initial_sp;
throw e;
};
} else { } else {
context.registers.di() = pop<uint16_t, true>(context); do_pops();
context.registers.si() = pop<uint16_t, true>(context);
context.registers.bp() = pop<uint16_t, true>(context);
context.registers.sp() += 2;
context.registers.bx() = pop<uint16_t, true>(context);
context.registers.dx() = pop<uint16_t, true>(context);
context.registers.cx() = pop<uint16_t, true>(context);
context.registers.ax() = pop<uint16_t, true>(context);
} }
} }
@@ -120,26 +133,38 @@ template <typename IntT, typename ContextT>
void pusha( void pusha(
ContextT &context ContextT &context
) { ) {
context.memory.preauthorise_stack_read(sizeof(IntT) * 8);
const IntT initial_sp = context.registers.sp(); const IntT initial_sp = context.registers.sp();
if constexpr (std::is_same_v<IntT, uint32_t>) { const auto do_pushes = [&] {
push<uint32_t, true>(context.registers.eax(), context); if constexpr (std::is_same_v<IntT, uint32_t>) {
push<uint32_t, true>(context.registers.ecx(), context); push<uint32_t, false>(context.registers.eax(), context);
push<uint32_t, true>(context.registers.edx(), context); push<uint32_t, false>(context.registers.ecx(), context);
push<uint32_t, true>(context.registers.ebx(), context); push<uint32_t, false>(context.registers.edx(), context);
push<uint32_t, true>(initial_sp, context); push<uint32_t, false>(context.registers.ebx(), context);
push<uint32_t, true>(context.registers.ebp(), context); push<uint32_t, false>(initial_sp, context);
push<uint32_t, true>(context.registers.esi(), context); push<uint32_t, false>(context.registers.ebp(), context);
push<uint32_t, true>(context.registers.edi(), context); push<uint32_t, false>(context.registers.esi(), context);
push<uint32_t, false>(context.registers.edi(), context);
} else {
push<uint16_t, false>(context.registers.ax(), context);
push<uint16_t, false>(context.registers.cx(), context);
push<uint16_t, false>(context.registers.dx(), context);
push<uint16_t, false>(context.registers.bx(), context);
push<uint16_t, false>(initial_sp, context);
push<uint16_t, false>(context.registers.bp(), context);
push<uint16_t, false>(context.registers.si(), context);
push<uint16_t, false>(context.registers.di(), context);
}
};
if(!uses_8086_exceptions(ContextT::model)) {
try {
do_pushes();
} catch (const Exception &e) {
context.registers.sp() = initial_sp;
throw e;
};
} else { } else {
push<uint16_t, true>(context.registers.ax(), context); do_pushes();
push<uint16_t, true>(context.registers.cx(), context);
push<uint16_t, true>(context.registers.dx(), context);
push<uint16_t, true>(context.registers.bx(), context);
push<uint16_t, true>(initial_sp, context);
push<uint16_t, true>(context.registers.bp(), context);
push<uint16_t, true>(context.registers.si(), context);
push<uint16_t, true>(context.registers.di(), context);
} }
} }
@@ -152,7 +177,11 @@ void enter(
const auto alloc_size = instruction.dynamic_storage_size(); const auto alloc_size = instruction.dynamic_storage_size();
const auto nesting_level = instruction.nesting_level() & 0x1f; const auto nesting_level = instruction.nesting_level() & 0x1f;
// Preauthorise contents that'll be fetched via BP. auto final_sp = context.registers.sp();
final_sp -= (nesting_level * sizeof(uint16_t)) + alloc_size + sizeof(uint16_t);
context.memory.preauthorise_write(Source::SS, final_sp, sizeof(IntT));
// Preauthorise contents that'll be copied via BP.
const auto copied_pointers = nesting_level - 2; const auto copied_pointers = nesting_level - 2;
if(copied_pointers > 0) { if(copied_pointers > 0) {
context.memory.preauthorise_read( context.memory.preauthorise_read(
@@ -160,10 +189,10 @@ void enter(
uint16_t(context.registers.bp() - size_t(copied_pointers) * sizeof(uint16_t)), uint16_t(context.registers.bp() - size_t(copied_pointers) * sizeof(uint16_t)),
uint32_t(size_t(copied_pointers) * sizeof(uint16_t)) // TODO: I don't think this can actually be 32 bit. uint32_t(size_t(copied_pointers) * sizeof(uint16_t)) // TODO: I don't think this can actually be 32 bit.
); );
}
// Preauthorise writes. // Preauthorise stack writes.
context.memory.preauthorise_stack_write(uint32_t(size_t(nesting_level) * sizeof(uint16_t))); // context.memory.preauthorise_stack_write(uint32_t(size_t(copied_pointers) * sizeof(uint16_t)));
}
// Push BP and grab the end of frame. // Push BP and grab the end of frame.
push<uint16_t, true>(context.registers.bp(), context); push<uint16_t, true>(context.registers.bp(), context);
@@ -175,15 +204,18 @@ void enter(
context.registers.bp() -= 2; context.registers.bp() -= 2;
const auto value = const auto value =
context.memory.template access<uint16_t, AccessType::PreauthorisedRead>(Source::SS, context.registers.bp()); context.memory.template access
push<uint16_t, true>(value, context); <uint16_t, AccessType::PreauthorisedRead>(Source::SS, context.registers.bp());
push<uint16_t, false>(value, context);
} }
push<uint16_t, true>(frame, context); push<uint16_t, false>(frame, context);
} }
// Set final BP. // Set final BP.
context.registers.bp() = frame; context.registers.bp() = frame;
context.registers.sp() -= alloc_size; context.registers.sp() -= alloc_size;
assert(final_sp == context.registers.sp());
} }
template <typename IntT, typename ContextT> template <typename IntT, typename ContextT>
@@ -192,11 +224,13 @@ void leave(
) { ) {
// TODO: should use StackAddressSize to determine assignment of bp to sp. // TODO: should use StackAddressSize to determine assignment of bp to sp.
if constexpr (std::is_same_v<IntT, uint32_t>) { if constexpr (std::is_same_v<IntT, uint32_t>) {
context.memory.preauthorise_read(Source::SS, context.registers.ebp(), sizeof(uint32_t));
context.registers.esp() = context.registers.ebp(); context.registers.esp() = context.registers.ebp();
context.registers.ebp() = pop<uint32_t, false>(context); context.registers.ebp() = pop<uint32_t, true>(context);
} else { } else {
context.memory.preauthorise_read(Source::SS, context.registers.bp(), sizeof(uint16_t));
context.registers.sp() = context.registers.bp(); context.registers.sp() = context.registers.bp();
context.registers.bp() = pop<uint16_t, false>(context); context.registers.bp() = pop<uint16_t, true>(context);
} }
} }

View File

@@ -8,6 +8,7 @@
#pragma once #pragma once
#include "Exceptions.hpp"
#include "Model.hpp" #include "Model.hpp"
#include <cstddef> #include <cstddef>
@@ -414,11 +415,11 @@ template <DataSize size> struct DataSizeType { using type = uint8_t; };
template <> struct DataSizeType<DataSize::Word> { using type = uint16_t; }; template <> struct DataSizeType<DataSize::Word> { using type = uint16_t; };
template <> struct DataSizeType<DataSize::DWord> { using type = uint32_t; }; template <> struct DataSizeType<DataSize::DWord> { using type = uint32_t; };
constexpr int byte_size(DataSize size) { constexpr int byte_size(const DataSize size) {
return (1 << int(size)) & 7; return (1 << int(size)) & 7;
} }
constexpr int bit_size(DataSize size) { constexpr int bit_size(const DataSize size) {
return (8 << int(size)) & 0x3f; return (8 << int(size)) & 0x3f;
} }
@@ -430,15 +431,15 @@ enum class AddressSize: uint8_t {
template <AddressSize size> struct AddressSizeType { using type = uint16_t; }; template <AddressSize size> struct AddressSizeType { using type = uint16_t; };
template <> struct AddressSizeType<AddressSize::b32> { using type = uint32_t; }; template <> struct AddressSizeType<AddressSize::b32> { using type = uint32_t; };
constexpr DataSize data_size(AddressSize size) { constexpr DataSize data_size(const AddressSize size) {
return DataSize(int(size) + 1); return DataSize(int(size) + 1);
} }
constexpr int byte_size(AddressSize size) { constexpr int byte_size(const AddressSize size) {
return 2 << int(size); return 2 << int(size);
} }
constexpr int bit_size(AddressSize size) { constexpr int bit_size(const AddressSize size) {
return 16 << int(size); return 16 << int(size);
} }
@@ -505,6 +506,10 @@ constexpr bool is_register(const Source source) {
constexpr bool is_segment_register(const Source source) { constexpr bool is_segment_register(const Source source) {
return is_register(source) && source >= Source::ES; return is_register(source) && source >= Source::ES;
} }
constexpr bool is_address(const Source source) {
// TODO: is there any reason not to swap DirectAddress and Immediate to simplify this?
return source == Source::Indirect || source == Source::IndirectNoBase || source == Source::DirectAddress;
}
enum class Repetition: uint8_t { enum class Repetition: uint8_t {
None, RepE, RepNE, Rep, None, RepE, RepNE, Rep,
@@ -512,7 +517,7 @@ enum class Repetition: uint8_t {
/// @returns @c true if @c operation supports repetition mode @c repetition; @c false otherwise. /// @returns @c true if @c operation supports repetition mode @c repetition; @c false otherwise.
template <Model model> template <Model model>
constexpr Operation rep_operation(Operation operation, Repetition repetition) { constexpr Operation rep_operation(const Operation operation, const Repetition repetition) {
switch(operation) { switch(operation) {
case Operation::IDIV: case Operation::IDIV:
if constexpr (model == Model::i8086) { if constexpr (model == Model::i8086) {
@@ -565,15 +570,15 @@ constexpr Operation rep_operation(Operation operation, Repetition repetition) {
class ScaleIndexBase { class ScaleIndexBase {
public: public:
constexpr ScaleIndexBase() noexcept = default; constexpr ScaleIndexBase() noexcept = default;
constexpr ScaleIndexBase(uint8_t sib) noexcept : sib_(sib) {} constexpr ScaleIndexBase(const uint8_t sib) noexcept : sib_(sib) {}
constexpr ScaleIndexBase(int scale, Source index, Source base) noexcept : constexpr ScaleIndexBase(const int scale, const Source index, const Source base) noexcept :
sib_(uint8_t( sib_(uint8_t(
scale << 6 | scale << 6 |
(int(index != Source::None ? index : Source::eSP) << 3) | (int(index != Source::None ? index : Source::eSP) << 3) |
int(base) int(base)
)) {} )) {}
constexpr ScaleIndexBase(Source index, Source base) noexcept : ScaleIndexBase(0, index, base) {} constexpr ScaleIndexBase(const Source index, const Source base) noexcept : ScaleIndexBase(0, index, base) {}
constexpr explicit ScaleIndexBase(Source base) noexcept : ScaleIndexBase(0, Source::None, base) {} constexpr explicit ScaleIndexBase(const Source base) noexcept : ScaleIndexBase(0, Source::None, base) {}
/// @returns the power of two by which to multiply @c index() before adding it to @c base(). /// @returns the power of two by which to multiply @c index() before adding it to @c base().
constexpr int scale() const { constexpr int scale() const {
@@ -634,18 +639,18 @@ static_assert(alignof(ScaleIndexBase) == 1);
class DataPointer { class DataPointer {
public: public:
/// Constricts a DataPointer referring to the given source; it shouldn't be ::Indirect. /// Constricts a DataPointer referring to the given source; it shouldn't be ::Indirect.
constexpr DataPointer(Source source) noexcept : source_(source) {} constexpr DataPointer(const Source source) noexcept : source_(source) {}
/// Constricts a DataPointer with a source of ::Indirect and the specified sib. /// Constricts a DataPointer with a source of ::Indirect and the specified sib.
constexpr DataPointer(ScaleIndexBase sib) noexcept : sib_(sib) {} constexpr DataPointer(const ScaleIndexBase sib) noexcept : sib_(sib) {}
/// Constructs a DataPointer with a source and SIB; use the source to indicate /// Constructs a DataPointer with a source and SIB; use the source to indicate
/// whether the base field of the SIB is effective. /// whether the base field of the SIB is effective.
constexpr DataPointer(Source source, ScaleIndexBase sib) noexcept : source_(source), sib_(sib) {} constexpr DataPointer(const Source source, const ScaleIndexBase sib) noexcept : source_(source), sib_(sib) {}
/// Constructs an indirect DataPointer referencing the given base, index and scale. /// Constructs an indirect DataPointer referencing the given base, index and scale.
/// Automatically maps Source::Indirect to Source::IndirectNoBase if base is Source::None. /// Automatically maps Source::Indirect to Source::IndirectNoBase if base is Source::None.
constexpr DataPointer(Source base, Source index, int scale) noexcept : constexpr DataPointer(const Source base, const Source index, const int scale) noexcept :
source_(base != Source::None ? Source::Indirect : Source::IndirectNoBase), source_(base != Source::None ? Source::Indirect : Source::IndirectNoBase),
sib_(scale, index, base) {} sib_(scale, index, base) {}
@@ -704,7 +709,7 @@ public:
using AddressT = ImmediateT; using AddressT = ImmediateT;
constexpr Instruction() noexcept = default; constexpr Instruction() noexcept = default;
constexpr Instruction(Operation operation) noexcept : constexpr Instruction(const Operation operation) noexcept :
Instruction( Instruction(
operation, operation,
Source::None, Source::None,
@@ -718,16 +723,16 @@ public:
0 0
) {} ) {}
constexpr Instruction( constexpr Instruction(
Operation operation, const Operation operation,
Source source, const Source source,
Source destination, const Source destination,
ScaleIndexBase sib, const ScaleIndexBase sib,
bool lock, const bool lock,
AddressSize address_size, const AddressSize address_size,
Source segment_override, const Source segment_override,
DataSize data_size, const DataSize data_size,
DisplacementT displacement, const DisplacementT displacement,
ImmediateT operand) noexcept : const ImmediateT operand) noexcept :
operation_(operation), operation_(operation),
mem_exts_source_(uint8_t( mem_exts_source_(uint8_t(
(int(address_size) << 7) | (int(address_size) << 7) |
@@ -763,6 +768,13 @@ public:
source_data_dest_sib_ |= (int(segment)&7) << 10; source_data_dest_sib_ |= (int(segment)&7) << 10;
} }
/// Instantiates an Instruction as a particular GPF. This was originally intended to support decoding of
/// overlong instructions.
constexpr Instruction(const ExceptionCode gpf_code) noexcept :
operation_(Operation::Invalid),
mem_exts_source_(1),
source_data_dest_sib_(uint16_t(gpf_code)) {}
/// @returns The number of bytes used for meaningful content within this class. A receiver must use at least @c sizeof(Instruction) bytes /// @returns The number of bytes used for meaningful content within this class. A receiver must use at least @c sizeof(Instruction) bytes
/// to store an @c Instruction but is permitted to reuse the trailing sizeof(Instruction) - packing_size() for any purpose it likes. Teleologically, /// to store an @c Instruction but is permitted to reuse the trailing sizeof(Instruction) - packing_size() for any purpose it likes. Teleologically,
/// this allows a denser packing of instructions into containers. /// this allows a denser packing of instructions into containers.
@@ -851,10 +863,21 @@ public:
} }
/// @returns The dynamic storage size argument supplied to an ENTER. /// @returns The dynamic storage size argument supplied to an ENTER.
constexpr ImmediateT dynamic_storage_size() const { constexpr ImmediateT dynamic_storage_size() const {
return offset(); return offset();
} }
/// If this Instruction's operation is ::Invalid, indicates whether the proper exception
/// to throw is a GPF rather than an InvalidOpcode.
constexpr bool invalid_is_gpf() const {
return mem_exts_source_ & 1;
}
/// If this Instruction represents a GPF, indicates the corresponding exception code.
constexpr ExceptionCode gpf_exception_code() const {
return ExceptionCode(source_data_dest_sib_);
}
// Standard comparison operator. // Standard comparison operator.
constexpr bool operator ==(const Instruction<type> &rhs) const { constexpr bool operator ==(const Instruction<type> &rhs) const {
if( operation_ != rhs.operation_ || if( operation_ != rhs.operation_ ||
@@ -932,12 +955,12 @@ static_assert(sizeof(Instruction<InstructionType::Bits16>) <= 10);
// //
/// @returns @c true if @c operation uses a @c displacement(). /// @returns @c true if @c operation uses a @c displacement().
bool has_displacement(Operation operation); bool has_displacement(const Operation operation);
/// @returns The maximum number of operands to print in a disassembly of @c operation; /// @returns The maximum number of operands to print in a disassembly of @c operation;
/// i.e. 2 for both source() and destination(), 1 for source() alone, 0 for neither. This is a maximum /// i.e. 2 for both source() and destination(), 1 for source() alone, 0 for neither. This is a maximum
/// only — if either source is Source::None then it should not be printed. /// only — if either source is Source::None then it should not be printed.
int max_displayed_operands(Operation operation); int max_displayed_operands(const Operation operation);
/// Provides the idiomatic name of the @c Operation given an operation @c DataSize and processor @c Model. /// Provides the idiomatic name of the @c Operation given an operation @c DataSize and processor @c Model.
std::string to_string(Operation, DataSize, Model); std::string to_string(Operation, DataSize, Model);
@@ -957,11 +980,11 @@ std::string to_string(Source, DataSize);
/// If @c operation_size is the default value of @c ::None, it'll be taken from the @c instruction. /// If @c operation_size is the default value of @c ::None, it'll be taken from the @c instruction.
template <InstructionType type> template <InstructionType type>
std::string to_string( std::string to_string(
DataPointer pointer, const DataPointer pointer,
Instruction<type> instruction, const Instruction<type> instruction,
int offset_length, const int offset_length,
int immediate_length, const int immediate_length,
DataSize operation_size = InstructionSet::x86::DataSize::None const DataSize operation_size = InstructionSet::x86::DataSize::None
); );
/// Provides the printable version of @c instruction. /// Provides the printable version of @c instruction.
@@ -972,8 +995,8 @@ 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. /// 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 <InstructionType type> template <InstructionType type>
std::string to_string( std::string to_string(
std::pair<int, Instruction<type>> instruction, const std::pair<int, Instruction<type>> instruction,
Model model, const Model model,
int offset_length = 0, const int offset_length = 0,
int immediate_length = 0); const int immediate_length = 0);
} }

View File

@@ -204,6 +204,7 @@ concept is_flow_controller_16 = requires(FlowControllerT controller) {
controller.halt(); controller.halt();
controller.wait(); controller.wait();
controller.repeat_last(); controller.repeat_last();
controller.cancel_repetition();
}; };
template <typename FlowControllerT, Model model> template <typename FlowControllerT, Model model>

View File

@@ -249,7 +249,6 @@ struct LinearMemory<InstructionSet::x86::Model::i80286>: public LinearPool<1 <<
typename InstructionSet::x86::Accessor<IntT, type>::type access( typename InstructionSet::x86::Accessor<IntT, type>::type access(
uint32_t address, uint32_t uint32_t address, uint32_t
) { ) {
// 80286: never split (probably?).
return *reinterpret_cast<IntT *>(&memory[address & address_mask_]); return *reinterpret_cast<IntT *>(&memory[address & address_mask_]);
} }
@@ -258,8 +257,6 @@ struct LinearMemory<InstructionSet::x86::Model::i80286>: public LinearPool<1 <<
uint32_t address, uint32_t uint32_t address, uint32_t
) const { ) const {
static_assert(!is_writeable(type)); static_assert(!is_writeable(type));
// 80286: never split (probably?).
return *reinterpret_cast<const IntT *>(&memory[address & address_mask_]); return *reinterpret_cast<const IntT *>(&memory[address & address_mask_]);
} }

View File

@@ -538,6 +538,9 @@ public:
void repeat_last() { void repeat_last() {
should_repeat_ = true; should_repeat_ = true;
} }
void cancel_repetition() {
should_repeat_ = false;
}
// Other actions. // Other actions.
void begin_instruction() { void begin_instruction() {
@@ -938,6 +941,7 @@ private:
KeyboardController<pc_model> &keyboard, KeyboardController<pc_model> &keyboard,
RTC &rtc RTC &rtc
) : ) :
flags(x86_model),
segments(registers, linear_memory), segments(registers, linear_memory),
memory(registers, segments, linear_memory), memory(registers, segments, linear_memory),
flow_controller(registers, segments), flow_controller(registers, segments),

View File

@@ -152,8 +152,12 @@ public:
); );
} }
void preauthorise_read(InstructionSet::x86::Source, uint16_t, uint32_t) {} void preauthorise_read(const InstructionSet::x86::Source descriptor, const uint16_t offset, const uint32_t size) {
void preauthorise_write(InstructionSet::x86::Source, uint16_t, uint32_t) {} segments_.descriptors[descriptor].template authorise<InstructionSet::x86::AccessType::Read, uint16_t>(offset, offset + size);
}
void preauthorise_write(const InstructionSet::x86::Source descriptor, const uint16_t offset, const uint32_t size) {
segments_.descriptors[descriptor].template authorise<InstructionSet::x86::AccessType::Write, uint16_t>(offset, offset + size);
}
// TODO: perform authorisation checks. // TODO: perform authorisation checks.

View File

@@ -31,11 +31,34 @@
namespace { namespace {
NSSet *const allowList = [NSSet setWithArray:@[
// Current execution failures, albeit all permitted:
// @"D4.json.gz", // AAM
// @"F6.7.json.gz", // IDIV byte
// @"F7.7.json.gz", // IDIV word
// @"00.json.gz",
// @"61.json.gz", // POPA
// @"C6.json.gz",
// @"C7.json.gz",
// @"C8.json.gz", // ENTER
// @"CD.json.gz",
// @"CE.json.gz", // INTO
// @"D8.json.gz", // Various floating point
// @"F6.7.json.gz", // IDIV
// @"FF.3.json.gz", // CALL far
// @"FF.5.json.gz"
]];
// MARK: - Test paths
// The tests themselves are not duplicated in this repository; // The tests themselves are not duplicated in this repository;
// provide their real path here. // provide their real paths here.
constexpr char TestSuiteHome8088[] = "/Users/thomasharte/Projects/8088/v1"; constexpr char TestSuiteHome8088[] = "/Users/thomasharte/Projects/8088/v1";
constexpr char TestSuiteHome80286[] = "/Users/thomasharte/Projects/80286/v1_real_mode"; constexpr char TestSuiteHome80286[] = "/Users/thomasharte/Projects/80286/v1_real_mode";
// MARK: - Virtual machine
using Flags = InstructionSet::x86::Flags; using Flags = InstructionSet::x86::Flags;
template <InstructionSet::x86::Model t_model> template <InstructionSet::x86::Model t_model>
@@ -148,7 +171,10 @@ struct IO {
template <InstructionSet::x86::Model t_model> template <InstructionSet::x86::Model t_model>
class FlowController { class FlowController {
public: public:
FlowController(InstructionSet::x86::Registers<t_model> &registers, PCCompatible::Segments<t_model, LinearMemory<t_model>> &segments) : FlowController(
InstructionSet::x86::Registers<t_model> &registers,
PCCompatible::Segments<t_model, LinearMemory<t_model>> &segments
) :
registers_(registers), segments_(segments) {} registers_(registers), segments_(segments) {}
// Requirements for perform. // Requirements for perform.
@@ -168,25 +194,38 @@ public:
registers_.ip() = address; registers_.ip() = address;
} }
void halt() {} void halt() {
is_halted_ = true;
}
void wait() {} void wait() {}
void repeat_last() { void repeat_last() {
should_repeat_ = true; should_repeat_ = true;
} }
void cancel_repetition() {
should_repeat_ = false;
}
// Other actions. // Other actions.
void begin_instruction() { void begin_instruction() {
should_repeat_ = false; is_halted_ = should_repeat_ = false;
} }
bool should_repeat() const { bool should_repeat() const {
return should_repeat_; return should_repeat_;
} }
bool is_halted() const {
return is_halted_;
}
void clear() {
should_repeat_ = is_halted_ = false;
}
private: private:
InstructionSet::x86::Registers<t_model> &registers_; InstructionSet::x86::Registers<t_model> &registers_;
PCCompatible::Segments<t_model, LinearMemory<t_model>> &segments_; PCCompatible::Segments<t_model, LinearMemory<t_model>> &segments_;
bool should_repeat_ = false; bool should_repeat_ = false;
bool is_halted_ = false;
}; };
struct CPUControl { struct CPUControl {
@@ -215,15 +254,19 @@ struct ExecutionSupport {
CPUControl cpu_control; CPUControl cpu_control;
ExecutionSupport(): ExecutionSupport():
flags(model),
memory(registers, segments, linear_memory), memory(registers, segments, linear_memory),
segments(registers, linear_memory), segments(registers, linear_memory),
flow_controller(registers, segments) {} flow_controller(registers, segments) {}
void clear() { void clear() {
linear_memory.clear(); linear_memory.clear();
flow_controller.clear();
} }
}; };
// MARK: - Test helpers
struct FailedExecution { struct FailedExecution {
std::string test_name; std::string test_name;
std::string reason; std::string reason;
@@ -242,15 +285,8 @@ std::vector<uint8_t> bytes(NSArray<NSNumber *> *encoding) {
return data; return data;
} }
NSArray<NSString *> *testFiles(const char *const home) { NSArray<NSString *> *test_files(const char *const home) {
NSString *const path = [NSString stringWithUTF8String:home]; NSString *const path = [NSString stringWithUTF8String:home];
NSSet *const allowList = [NSSet setWithArray:@[
// Current execution failures, albeit all permitted:
// @"D4.json.gz", // AAM
// @"F6.7.json.gz", // IDIV byte
// @"F7.7.json.gz", // IDIV word
]];
NSSet *ignoreList = nil; NSSet *ignoreList = nil;
NSArray<NSString *> *files = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:path error:nil]; NSArray<NSString *> *files = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:path error:nil];
@@ -275,7 +311,7 @@ NSArray<NSString *> *testFiles(const char *const home) {
return [fullPaths sortedArrayUsingSelector:@selector(compare:)]; return [fullPaths sortedArrayUsingSelector:@selector(compare:)];
} }
NSArray<NSDictionary *> *testsInFile(NSString *file) { NSArray<NSDictionary *> *tests_in_file(NSString *file) {
NSData *data = [NSData dataWithContentsOfGZippedFile:file]; NSData *data = [NSData dataWithContentsOfGZippedFile:file];
return [NSJSONSerialization JSONObjectWithData:data options:0 error:nil]; return [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
} }
@@ -287,6 +323,8 @@ NSDictionary *metadata(const char *home) {
template <InstructionSet::x86::Model t_model> template <InstructionSet::x86::Model t_model>
void populate(InstructionSet::x86::Registers<t_model> &registers, Flags &flags, NSDictionary *value) { void populate(InstructionSet::x86::Registers<t_model> &registers, Flags &flags, NSDictionary *value) {
registers.reset();
registers.ax() = [value[@"ax"] intValue]; registers.ax() = [value[@"ax"] intValue];
registers.bx() = [value[@"bx"] intValue]; registers.bx() = [value[@"bx"] intValue];
registers.cx() = [value[@"cx"] intValue]; registers.cx() = [value[@"cx"] intValue];
@@ -324,6 +362,8 @@ void populate(InstructionSet::x86::Registers<t_model> &registers, Flags &flags,
); );
} }
// MARK: - Test runners; execution
template <InstructionSet::x86::Model t_model> template <InstructionSet::x86::Model t_model>
void apply_execution_test( void apply_execution_test(
ExecutionSupport<t_model> &execution_support, ExecutionSupport<t_model> &execution_support,
@@ -332,10 +372,24 @@ void apply_execution_test(
NSDictionary *test, NSDictionary *test,
NSDictionary *metadata NSDictionary *metadata
) { ) {
// NSLog(@"%@", test[@"hash"]);
// if(![test[@"hash"] isEqualToString:@"ed7087ccf6036e30365a290ac7598d7a3cb93b43"]) {
// return;
// }
InstructionSet::x86::Decoder<t_model> decoder; InstructionSet::x86::Decoder<t_model> decoder;
const auto data = bytes(test[@"bytes"]); const auto data = bytes(test[@"bytes"]);
const auto decoded = decoder.decode(data.data(), data.size()); const auto decoded = decoder.decode(data.data(), data.size());
if(decoded.first < 0) {
FailedExecution failure;
failure.instruction = decoded.second;
failure.test_name = std::string([[test[@"name"] stringByAppendingFormat:@"; hash: %@", test[@"hash"]] UTF8String]);
failure.reason = "Failed to decode";
execution_failures.push_back(std::move(failure));
return;
}
execution_support.clear(); execution_support.clear();
const uint16_t flags_mask = metadata[@"flags-mask"] ? [metadata[@"flags-mask"] intValue] : 0xffff; const uint16_t flags_mask = metadata[@"flags-mask"] ? [metadata[@"flags-mask"] intValue] : 0xffff;
@@ -343,7 +397,7 @@ void apply_execution_test(
NSDictionary *const final_state = test[@"final"]; NSDictionary *const final_state = test[@"final"];
// Apply initial state. // Apply initial state.
Flags initial_flags; Flags initial_flags(t_model);
for(NSArray<NSNumber *> *ram in initial_state[@"ram"]) { for(NSArray<NSNumber *> *ram in initial_state[@"ram"]) {
execution_support.linear_memory.seed([ram[0] intValue], [ram[1] intValue]); execution_support.linear_memory.seed([ram[0] intValue], [ram[1] intValue]);
} }
@@ -364,8 +418,6 @@ void apply_execution_test(
execution_support.registers.ip() += decoded.first; execution_support.registers.ip() += decoded.first;
do { do {
execution_support.flow_controller.begin_instruction(); execution_support.flow_controller.begin_instruction();
// TODO: catch and process exceptions, which I think means better factoring
// re: PCCompatible/instruction set.
InstructionSet::x86::perform( InstructionSet::x86::perform(
decoded.second, decoded.second,
execution_support, execution_support,
@@ -373,16 +425,25 @@ void apply_execution_test(
); );
} while (execution_support.flow_controller.should_repeat()); } while (execution_support.flow_controller.should_repeat());
// If this is the 80286 test set then there was also a HLT. Act as if that happened.
// Unless the processor was already halted.
if constexpr (t_model == InstructionSet::x86::Model::i80286) {
if(!execution_support.flow_controller.is_halted()) {
++execution_support.registers.ip();
}
}
// Compare final state. // Compare final state.
InstructionSet::x86::Registers<t_model> intended_registers; InstructionSet::x86::Registers<t_model> intended_registers;
InstructionSet::x86::Flags intended_flags; InstructionSet::x86::Flags intended_flags(t_model);
bool ramEqual = true; bool ramEqual = true;
int mask_position = 0; int mask_position = 0;
for(NSArray<NSNumber *> *ram in final_state[@"ram"]) { for(NSArray<NSNumber *> *ram in final_state[@"ram"]) {
const uint32_t address = [ram[0] intValue]; const uint32_t address = [ram[0] intValue];
const auto value = const auto value =
execution_support.linear_memory.template access<uint8_t, InstructionSet::x86::AccessType::Read>(address, address); execution_support.linear_memory.template access<uint8_t, InstructionSet::x86::AccessType::Read>
(address, address);
if((mask_position != 1) && value == [ram[1] intValue]) { if((mask_position != 1) && value == [ram[1] intValue]) {
continue; continue;
@@ -480,23 +541,26 @@ void apply_execution_test(
// Record a failure. // Record a failure.
FailedExecution failure; FailedExecution failure;
failure.instruction = decoded.second; failure.instruction = decoded.second;
failure.test_name = std::string([test[@"name"] UTF8String]); failure.test_name = std::string([[test[@"name"] stringByAppendingFormat:@"; hash: %@", test[@"hash"]] UTF8String]);
NSMutableArray<NSString *> *reasons = [[NSMutableArray alloc] init]; NSMutableArray<NSString *> *reasons = [[NSMutableArray alloc] init];
if(!flagsEqual) { if(!flagsEqual) {
Flags difference; Flags difference(t_model);
difference.set((intended_flags.get() ^ execution_support.flags.get()) & flags_mask); difference.set((intended_flags.get() ^ execution_support.flags.get()) & flags_mask);
[reasons addObject: [reasons addObject:
[NSString stringWithFormat:@"flags differ; errors in %s", [NSString stringWithFormat:@"flags differ; errors in %s due to final state %s",
difference.to_string().c_str()]]; difference.to_string().c_str(), execution_support.flags.to_string().c_str()]];
} }
if(!registersEqual) { if(!registersEqual) {
NSMutableArray<NSString *> *registers = [[NSMutableArray alloc] init]; NSMutableArray<NSString *> *registers = [[NSMutableArray alloc] init];
bool is_first = true;
#define Reg(x) \ #define Reg(x) \
if(intended_registers.x() != execution_support.registers.x()) \ if(intended_registers.x() != execution_support.registers.x()) { \
[registers addObject: \ [registers addObject: \
[NSString stringWithFormat: \ [NSString stringWithFormat: \
@#x" is %04x rather than %04x", execution_support.registers.x(), intended_registers.x()]]; @#x" is %04x %@ %04x", execution_support.registers.x(), is_first ? @"but should have been" : @"not", intended_registers.x()]]; \
is_first = false; \
}
Reg(ax); Reg(ax);
Reg(cx); Reg(cx);
@@ -528,12 +592,12 @@ void apply_execution_test(
template <InstructionSet::x86::Model t_model> template <InstructionSet::x86::Model t_model>
void test_execution(const char *const home) { void test_execution(const char *const home) {
NSDictionary *metadatas = metadata(home); NSDictionary *metadatas = metadata(home);
NSMutableArray<NSString *> *failures = [[NSMutableArray alloc] init]; NSMutableDictionary<NSString *, NSNumber *> *failures = [[NSMutableDictionary alloc] init];
std::vector<FailedExecution> execution_failures; std::vector<FailedExecution> execution_failures;
std::vector<FailedExecution> permitted_failures; std::vector<FailedExecution> permitted_failures;
auto execution_support = std::make_unique<ExecutionSupport<t_model>>(); auto execution_support = std::make_unique<ExecutionSupport<t_model>>();
for(NSString *file in testFiles(home)) @autoreleasepool { for(NSString *file in test_files(home)) @autoreleasepool {
const auto failures_before = execution_failures.size(); const auto failures_before = execution_failures.size();
// Determine the metadata key. // Determine the metadata key.
@@ -548,14 +612,12 @@ void test_execution(const char *const home) {
test_metadata[@"reg"][[NSString stringWithFormat:@"%c", [name characterAtIndex:first_dot.location+1]]]; test_metadata[@"reg"][[NSString stringWithFormat:@"%c", [name characterAtIndex:first_dot.location+1]]];
} }
// int index = 0; for(NSDictionary *test in tests_in_file(file)) {
for(NSDictionary *test in testsInFile(file)) {
apply_execution_test(*execution_support, execution_failures, permitted_failures, test, test_metadata); apply_execution_test(*execution_support, execution_failures, permitted_failures, test, test_metadata);
// ++index;
} }
if (execution_failures.size() != failures_before) { if (execution_failures.size() != failures_before) {
[failures addObject:file]; failures[file] = @([failures[file] intValue] + execution_failures.size() - failures_before);
} }
} }
@@ -569,28 +631,25 @@ void test_execution(const char *const home) {
NSLog(@"Permitted failure of %s — %s", failure.test_name.c_str(), failure.reason.c_str()); NSLog(@"Permitted failure of %s — %s", failure.test_name.c_str(), failure.reason.c_str());
} }
NSLog(@"Files with failures, permitted or otherwise, were: %@", failures); NSLog(@"Files with failures, permitted or otherwise, were the following %@: %@", @(failures.count), failures);
}
} }
@interface i8088Tests : XCTestCase // MARK: - Test runners; decoding
@end
@implementation i8088Tests template <InstructionSet::x86::Model model, InstructionSet::x86::InstructionType type>
NSString *to_string(
using Instruction = InstructionSet::x86::Instruction<InstructionSet::x86::InstructionType::Bits16>; const std::pair<int, InstructionSet::x86::Instruction<type>> &instruction,
- (NSString *) int offsetLength,
toString:(const std::pair<int, Instruction> &)instruction int immediateLength
offsetLength:(int)offsetLength ) {
immediateLength:(int)immediateLength const auto operation = to_string(instruction, model, offsetLength, immediateLength);
{
const auto operation = to_string(instruction, InstructionSet::x86::Model::i8086, offsetLength, immediateLength);
return [[NSString stringWithUTF8String:operation.c_str()] return [[NSString stringWithUTF8String:operation.c_str()]
stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
} }
- (bool)applyDecodingTest:(NSDictionary *)test file:(NSString *)file assert:(BOOL)assert { template <InstructionSet::x86::Model model>
InstructionSet::x86::Decoder<InstructionSet::x86::Model::i8086> decoder; bool apply_decoding_test(NSDictionary *test, NSString *file, BOOL assert) {
InstructionSet::x86::Decoder<model> decoder;
// Build a vector of the instruction bytes; this makes manual step debugging easier. // Build a vector of the instruction bytes; this makes manual step debugging easier.
const auto data = bytes(test[@"bytes"]); const auto data = bytes(test[@"bytes"]);
@@ -603,7 +662,10 @@ using Instruction = InstructionSet::x86::Instruction<InstructionSet::x86::Instru
}; };
const auto decoded = decoder.decode(data.data(), data.size()); const auto decoded = decoder.decode(data.data(), data.size());
const bool sizeMatched = decoded.first == data.size(); const bool sizeMatched =
(model == InstructionSet::x86::Model::i8086) ?
(decoded.first == data.size()) :
(decoded.first == data.size() - 1); // The 80286 instruction set adds a HLT after every instruction.
if(assert) { if(assert) {
XCTAssert( XCTAssert(
sizeMatched, sizeMatched,
@@ -622,12 +684,12 @@ using Instruction = InstructionSet::x86::Instruction<InstructionSet::x86::Instru
// The decoder doesn't preserve the original offset length, which makes no functional difference but // 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. // does affect the way that offsets are printed in the test set.
NSSet<NSString *> *decodings = [NSSet setWithObjects: NSSet<NSString *> *decodings = [NSSet setWithObjects:
[self toString:decoded offsetLength:4 immediateLength:4], to_string<model>(decoded, 4, 4),
[self toString:decoded offsetLength:2 immediateLength:4], to_string<model>(decoded, 2, 4),
[self toString:decoded offsetLength:0 immediateLength:4], to_string<model>(decoded, 0, 4),
[self toString:decoded offsetLength:4 immediateLength:2], to_string<model>(decoded, 4, 2),
[self toString:decoded offsetLength:2 immediateLength:2], to_string<model>(decoded, 2, 2),
[self toString:decoded offsetLength:0 immediateLength:2], to_string<model>(decoded, 0, 2),
nil]; nil];
auto compare_decoding = [&](NSString *name) -> bool { auto compare_decoding = [&](NSString *name) -> bool {
@@ -672,36 +734,53 @@ using Instruction = InstructionSet::x86::Instruction<InstructionSet::x86::Instru
return isEqual; return isEqual;
} }
- (void)printFailures:(NSArray<NSString *> *)failures { template <InstructionSet::x86::Model model>
NSLog( void test_decoding(const char *home) {
@"%ld failures out of %ld tests: %@",
failures.count,
testFiles(TestSuiteHome8088).count,
[failures sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)]);
}
- (void)testDecoding {
NSMutableArray<NSString *> *failures = [[NSMutableArray alloc] init]; NSMutableArray<NSString *> *failures = [[NSMutableArray alloc] init];
for(NSString *file in testFiles(TestSuiteHome8088)) @autoreleasepool { for(NSString *file in test_files(home)) @autoreleasepool {
for(NSDictionary *test in testsInFile(file)) { for(NSDictionary *test in tests_in_file(file)) {
// A single failure per instruction is fine. // A single failure per instruction is fine.
if(![self applyDecodingTest:test file:file assert:YES]) { if(!apply_decoding_test<model>(test, file, YES)) {
[failures addObject:file]; [failures addObject:file];
// Attempt a second decoding, to provide a debugger hook. // Attempt a second decoding, to provide a debugger hook.
[self applyDecodingTest:test file:file assert:NO]; apply_decoding_test<model>(test, file, NO);
break; break;
} }
} }
} }
[self printFailures:failures]; NSLog(
@"%ld failures out of %ld tests: %@",
failures.count,
test_files(TestSuiteHome8088).count,
[failures sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)]);
}
}
@interface i8088Tests : XCTestCase
@end
@implementation i8088Tests
using Instruction = InstructionSet::x86::Instruction<InstructionSet::x86::InstructionType::Bits16>;
// MARK: - 8088
- (void)testDecoding8088 {
test_decoding<InstructionSet::x86::Model::i8086>(TestSuiteHome8088);
} }
- (void)testExecution8088 { - (void)testExecution8088 {
test_execution<InstructionSet::x86::Model::i8086>(TestSuiteHome8088); test_execution<InstructionSet::x86::Model::i8086>(TestSuiteHome8088);
} }
// MARK: - 80286
- (void)testDecoding80286 {
test_decoding<InstructionSet::x86::Model::i80286>(TestSuiteHome80286);
}
- (void)testExecution80286 { - (void)testExecution80286 {
test_execution<InstructionSet::x86::Model::i80286>(TestSuiteHome80286); test_execution<InstructionSet::x86::Model::i80286>(TestSuiteHome80286);
} }