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

Compare commits

...

46 Commits

Author SHA1 Message Date
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
18 changed files with 528 additions and 185 deletions

View File

@@ -196,6 +196,7 @@ std::pair<int, typename Decoder<model>::InstructionT> Decoder<model>::decode(
} else {
immediate(Operation::PUSH, DataSize::Byte);
operation_size_ = data_size_;
sign_extend_operand_ = true;
}
break;
case 0x6b:
@@ -736,9 +737,8 @@ std::pair<int, typename Decoder<model>::InstructionT> Decoder<model>::decode(
switch(reg) {
default:
// case 1 is treated as another form of TEST on the 8086.
// (and, I guess, the 80186?)
if constexpr (model >= Model::i80286) {
// case 1 is treated as another form of TEST through to at least the 80286.
if constexpr (model >= Model::i80386) {
return undefined();
}
[[fallthrough]];
@@ -799,13 +799,20 @@ std::pair<int, typename Decoder<model>::InstructionT> Decoder<model>::decode(
switch(reg) {
default:
if constexpr (model == Model::i8086) {
if(source_ == Source::eCX) {
set(Operation::SETMOC);
} else {
set(Operation::SETMO);
}
} else {
switch(model) {
case Model::i8086:
if(source_ == Source::eCX) {
set(Operation::SETMOC);
} else {
set(Operation::SETMO);
}
break;
case Model::i80286:
set(Operation::SAL);
break;
default:
return undefined();
}
break;
@@ -1017,8 +1024,13 @@ std::pair<int, typename Decoder<model>::InstructionT> Decoder<model>::decode(
}
}
} else {
// Provide a genuine measure of further bytes required.
return std::make_pair(-(outstanding_bytes - bytes_to_consume), InstructionT());
// Provide a genuine measure of further bytes required, or post a bad instruction
// 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 +1049,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
// 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(
consumed_,
InstructionT(
@@ -1058,14 +1082,7 @@ std::pair<int, typename Decoder<model>::InstructionT> Decoder<model>::decode(
// Check for a too-long instruction.
if(consumed_ == max_instruction_length) {
std::pair<int, InstructionT> result;
if(max_instruction_length == 65536) {
result = std::make_pair(consumed_, InstructionT(Operation::NOP));
} else {
result = std::make_pair(consumed_, InstructionT());
}
reset_parsing();
return result;
return overlong();
}
// i.e. not done yet.

View File

@@ -349,6 +349,7 @@ private:
phase_ = Phase::DisplacementOrOperand;
displacement_size_ = DataSize::Word;
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').
@@ -367,6 +368,15 @@ private:
reset_parsing();
return result;
}
std::pair<int, typename Decoder<model>::InstructionT> overlong() {
reset_parsing();
if(consumed_ == 65536) {
return std::make_pair(consumed_, InstructionT(Operation::NOP));
} else {
return std::make_pair(consumed_, InstructionT());
}
}
};
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().
if(type == AccessType::Read && executable() && !readable()) {
@@ -90,10 +96,6 @@ struct SegmentDescriptor {
if(type == AccessType::Write && !executable() && !writeable()) {
throw_exception();
}
if(begin < bounds_.begin || end >= bounds_.end) {
throw_exception();
}
}
void validate_as(const Source segment) const {

View File

@@ -82,7 +82,7 @@ constexpr bool posts_next_ip(const Vector vector) {
case SingleStep:
case Breakpoint:
case Overflow:
return false;
return true;
}
}

View File

@@ -81,6 +81,18 @@ class Flags {
public:
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.
template <Flag flag_v> bool flag() const {
switch(flag_v) {
@@ -177,7 +189,7 @@ public:
uint16_t get() const {
return
0xf002 |
forced_set_ |
(flag<Flag::Carry>() ? FlagValue::Carry : 0) |
(flag<Flag::AuxiliaryCarry>() ? FlagValue::AuxiliaryCarry : 0) |
@@ -232,6 +244,9 @@ private:
// Odd number of bits => set; even => unset.
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>
void imul(
void imul_double(
modify_t<IntT> destination_high,
modify_t<IntT> destination_low,
read_t<IntT> source,
@@ -167,6 +167,22 @@ void imul(
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>
void divide_error(ContextT &context) {
// 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 constexpr (add) {
ax.halves.low += 6;
if constexpr (ContextT::model <= Model::i80186) {
ax.halves.low += 6;
} else {
ax.full += 6;
}
++ax.halves.high;
} else {
ax.halves.low -= 6;
if constexpr (ContextT::model <= Model::i80186) {
ax.halves.low -= 6;
} else {
ax.full -= 6;
}
--ax.halves.high;
}
context.flags.template set_from<Flag::Carry, Flag::AuxiliaryCarry>(1);

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.
// 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.
@@ -241,18 +247,21 @@ template <
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::IDIV: Primitive::idiv<false, IntT>(pair_high(), pair_low(), source_r(), context); return;
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);
break;
} else {
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;
@@ -431,7 +440,7 @@ template <
break;
} else {
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;
@@ -453,9 +462,14 @@ template <
}
} break;
case Operation::PUSH:
Primitive::push<IntT, false>(source_rmw(), context); // PUSH SP modifies SP before pushing it;
// hence PUSH is sometimes read-modify-write.
break;
if constexpr (ContextT::model >= Model::i80286) {
Primitive::push<IntT, false>(source_r(), context);
} 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:
if constexpr (std::is_same_v<IntT, uint16_t> || std::is_same_v<IntT, uint32_t>) {
@@ -570,7 +584,6 @@ template <
// LSL
// LTR
// STR
// IMUL_3
// LOADALL
}
@@ -592,7 +605,7 @@ void perform(
const Instruction<type> &instruction,
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);
};
@@ -647,7 +660,8 @@ void perform(
// This is reachable only if the data and address size combination in use isn't available
// 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.
@@ -678,6 +692,7 @@ void perform(
);
return;
} catch (const InstructionSet::x86::Exception exception) {
context.flow_controller.cancel_repetition();
fault(exception, context, source_ip);
}
}

View File

@@ -51,8 +51,28 @@ void cmps(
return;
}
IntT lhs = context.memory.template access<IntT, AccessType::Read>(instruction.data_segment(), eSI);
const IntT rhs = context.memory.template access<IntT, AccessType::Read>(Source::ES, eDI);
IntT lhs, rhs;
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);
eDI += context.flags.template direction<AddressT>() * sizeof(IntT);
@@ -72,11 +92,22 @@ void scas(
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);
Primitive::sub<false, AccessType::Read, IntT>(eAX, rhs, context);
repeat<AddressT, repetition>(eCX, context);
}
@@ -92,9 +123,19 @@ void lods(
return;
}
eAX = context.memory.template access<IntT, AccessType::Read>(instruction.data_segment(), eSI);
eSI += context.flags.template direction<AddressT>() * sizeof(IntT);
if(!uses_8086_exceptions(ContextT::model)) {
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);
}
@@ -110,12 +151,33 @@ void movs(
return;
}
context.memory.template access<IntT, AccessType::Write>(Source::ES, eDI) =
context.memory.template access<IntT, AccessType::Read>(instruction.data_segment(), eSI);
if constexpr (!uses_8086_exceptions(ContextT::model)) {
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);
eDI += context.flags.template direction<AddressT>() * sizeof(IntT);
repeat<AddressT, repetition>(eCX, context);
}
@@ -123,16 +185,33 @@ template <typename IntT, typename AddressT, Repetition repetition, typename Cont
void stos(
AddressT &eCX,
AddressT &eDI,
IntT &eAX,
const IntT eAX,
ContextT &context
) {
if(repetition_over<AddressT, repetition>(eCX)) {
return;
}
context.memory.template access<IntT, AccessType::Write>(Source::ES, eDI) = eAX;
eDI += context.flags.template direction<AddressT>() * sizeof(IntT);
if constexpr (!uses_8086_exceptions(ContextT::model)) {
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);
}
@@ -148,12 +227,26 @@ void outs(
return;
}
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);
// TODO: break down as two potentially-throwing steps.
if(!uses_8086_exceptions(ContextT::model)) {
try {
context.io.template out<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);
}
@@ -168,9 +261,22 @@ void ins(
return;
}
context.memory.template access<IntT, AccessType::Write>(Source::ES, eDI) = context.io.template in<IntT>(port);
eDI += context.flags.template direction<AddressT>() * sizeof(IntT);
// TODO: break down as two potentially-throwing steps.
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);
}

View File

@@ -50,12 +50,18 @@ uint32_t address(
if constexpr (instruction_type(ContextT::model) != InstructionType::Bits16) {
address <<= pointer.scale();
}
// printf("%d + %d", address, instruction.offset());
address += instruction.offset();
if constexpr (source == Source::IndirectNoBase) {
// printf("\n");
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;
@@ -207,6 +213,10 @@ typename Accessor<IntT, access>::type resolve(
// Do it and exit.
//
// 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));
}

View File

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

View File

@@ -94,25 +94,38 @@ template <typename IntT, typename ContextT>
void popa(
ContextT &context
) {
context.memory.preauthorise_stack_read(sizeof(IntT) * 8);
if constexpr (std::is_same_v<IntT, uint32_t>) {
context.registers.edi() = pop<uint32_t, true>(context);
context.registers.esi() = pop<uint32_t, true>(context);
context.registers.ebp() = pop<uint32_t, true>(context);
context.registers.esp() += 4;
context.registers.ebx() = pop<uint32_t, true>(context);
context.registers.edx() = pop<uint32_t, true>(context);
context.registers.ecx() = pop<uint32_t, true>(context);
context.registers.eax() = pop<uint32_t, true>(context);
const auto initial_sp = context.registers.sp();
const auto do_pops = [&] {
if constexpr (std::is_same_v<IntT, uint32_t>) {
context.registers.edi() = pop<uint32_t, false>(context);
context.registers.esi() = pop<uint32_t, false>(context);
context.registers.ebp() = pop<uint32_t, false>(context);
context.registers.esp() += 4;
context.registers.ebx() = pop<uint32_t, false>(context);
context.registers.edx() = pop<uint32_t, false>(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 {
context.registers.di() = pop<uint16_t, true>(context);
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);
do_pops();
}
}
@@ -120,26 +133,38 @@ template <typename IntT, typename ContextT>
void pusha(
ContextT &context
) {
context.memory.preauthorise_stack_read(sizeof(IntT) * 8);
const IntT initial_sp = context.registers.sp();
if constexpr (std::is_same_v<IntT, uint32_t>) {
push<uint32_t, true>(context.registers.eax(), context);
push<uint32_t, true>(context.registers.ecx(), context);
push<uint32_t, true>(context.registers.edx(), context);
push<uint32_t, true>(context.registers.ebx(), context);
push<uint32_t, true>(initial_sp, context);
push<uint32_t, true>(context.registers.ebp(), context);
push<uint32_t, true>(context.registers.esi(), context);
push<uint32_t, true>(context.registers.edi(), context);
const auto do_pushes = [&] {
if constexpr (std::is_same_v<IntT, uint32_t>) {
push<uint32_t, false>(context.registers.eax(), context);
push<uint32_t, false>(context.registers.ecx(), context);
push<uint32_t, false>(context.registers.edx(), context);
push<uint32_t, false>(context.registers.ebx(), context);
push<uint32_t, false>(initial_sp, context);
push<uint32_t, false>(context.registers.ebp(), 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 {
push<uint16_t, true>(context.registers.ax(), context);
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);
do_pushes();
}
}
@@ -152,7 +177,11 @@ void enter(
const auto alloc_size = instruction.dynamic_storage_size();
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;
if(copied_pointers > 0) {
context.memory.preauthorise_read(
@@ -160,10 +189,10 @@ void enter(
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.
);
}
// Preauthorise writes.
context.memory.preauthorise_stack_write(uint32_t(size_t(nesting_level) * sizeof(uint16_t)));
// Preauthorise stack writes.
// context.memory.preauthorise_stack_write(uint32_t(size_t(copied_pointers) * sizeof(uint16_t)));
}
// Push BP and grab the end of frame.
push<uint16_t, true>(context.registers.bp(), context);
@@ -175,15 +204,18 @@ void enter(
context.registers.bp() -= 2;
const auto value =
context.memory.template access<uint16_t, AccessType::PreauthorisedRead>(Source::SS, context.registers.bp());
push<uint16_t, true>(value, context);
context.memory.template access
<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.
context.registers.bp() = frame;
context.registers.sp() -= alloc_size;
assert(final_sp == context.registers.sp());
}
template <typename IntT, typename ContextT>
@@ -192,11 +224,13 @@ void leave(
) {
// TODO: should use StackAddressSize to determine assignment of bp to sp.
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.ebp() = pop<uint32_t, false>(context);
context.registers.ebp() = pop<uint32_t, true>(context);
} else {
context.memory.preauthorise_read(Source::SS, context.registers.bp(), sizeof(uint16_t));
context.registers.sp() = context.registers.bp();
context.registers.bp() = pop<uint16_t, false>(context);
context.registers.bp() = pop<uint16_t, true>(context);
}
}

View File

@@ -505,6 +505,10 @@ constexpr bool is_register(const Source source) {
constexpr bool is_segment_register(const Source source) {
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 {
None, RepE, RepNE, Rep,

View File

@@ -204,6 +204,7 @@ concept is_flow_controller_16 = requires(FlowControllerT controller) {
controller.halt();
controller.wait();
controller.repeat_last();
controller.cancel_repetition();
};
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(
uint32_t address, uint32_t
) {
// 80286: never split (probably?).
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
) const {
static_assert(!is_writeable(type));
// 80286: never split (probably?).
return *reinterpret_cast<const IntT *>(&memory[address & address_mask_]);
}

View File

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

View File

@@ -152,8 +152,12 @@ public:
);
}
void preauthorise_read(InstructionSet::x86::Source, uint16_t, uint32_t) {}
void preauthorise_write(InstructionSet::x86::Source, uint16_t, uint32_t) {}
void preauthorise_read(const InstructionSet::x86::Source descriptor, const uint16_t offset, const uint32_t size) {
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.

View File

@@ -31,11 +31,47 @@
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",
// @"2F.json.gz", // DAS
// @"61.json.gz", // POPA
// @"69.json.gz", // IMUL
// @"81.0.json.gz", // ADD
// @"81.1.json.gz", // OR
// @"81.2.json.gz", // ADC
// @"81.3.json.gz", // SBB
// @"81.4.json.gz", // AND
// @"81.5.json.gz",
// @"81.6.json.gz",
// @"81.7.json.gz",
// @"9A.json.gz", // CALL
// @"C6.json.gz", // MOV byte failure to spot invalids
// @"C7.json.gz", // MOV WORD failure to spot invalids
// @"C8.json.gz", // ENTER
// @"CD.json.gz",
// @"CE.json.gz", // INTO
// @"D8.json.gz", // Various floating point
// @"EA.json.gz", // JMP aa:bb
// @"F6.7.json.gz", // IDIV
// @"F7.0.json.gz", // TEST
// @"F7.1.json.gz", // TEST
// @"FF.3.json.gz", // CALL far
]];
// MARK: - Test paths
// 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 TestSuiteHome80286[] = "/Users/thomasharte/Projects/80286/v1_real_mode";
// MARK: - Virtual machine
using Flags = InstructionSet::x86::Flags;
template <InstructionSet::x86::Model t_model>
@@ -148,7 +184,10 @@ struct IO {
template <InstructionSet::x86::Model t_model>
class FlowController {
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) {}
// Requirements for perform.
@@ -168,25 +207,38 @@ public:
registers_.ip() = address;
}
void halt() {}
void halt() {
is_halted_ = true;
}
void wait() {}
void repeat_last() {
should_repeat_ = true;
}
void cancel_repetition() {
should_repeat_ = false;
}
// Other actions.
void begin_instruction() {
should_repeat_ = false;
is_halted_ = should_repeat_ = false;
}
bool should_repeat() const {
return should_repeat_;
}
bool is_halted() const {
return is_halted_;
}
void clear() {
should_repeat_ = is_halted_ = false;
}
private:
InstructionSet::x86::Registers<t_model> &registers_;
PCCompatible::Segments<t_model, LinearMemory<t_model>> &segments_;
bool should_repeat_ = false;
bool is_halted_ = false;
};
struct CPUControl {
@@ -215,15 +267,19 @@ struct ExecutionSupport {
CPUControl cpu_control;
ExecutionSupport():
flags(model),
memory(registers, segments, linear_memory),
segments(registers, linear_memory),
flow_controller(registers, segments) {}
void clear() {
linear_memory.clear();
flow_controller.clear();
}
};
// MARK: - Test helpers
struct FailedExecution {
std::string test_name;
std::string reason;
@@ -242,15 +298,8 @@ std::vector<uint8_t> bytes(NSArray<NSNumber *> *encoding) {
return data;
}
NSArray<NSString *> *testFiles(const char *const home) {
NSArray<NSString *> *test_files(const char *const 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;
NSArray<NSString *> *files = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:path error:nil];
@@ -275,7 +324,7 @@ NSArray<NSString *> *testFiles(const char *const home) {
return [fullPaths sortedArrayUsingSelector:@selector(compare:)];
}
NSArray<NSDictionary *> *testsInFile(NSString *file) {
NSArray<NSDictionary *> *tests_in_file(NSString *file) {
NSData *data = [NSData dataWithContentsOfGZippedFile:file];
return [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
}
@@ -287,6 +336,8 @@ NSDictionary *metadata(const char *home) {
template <InstructionSet::x86::Model t_model>
void populate(InstructionSet::x86::Registers<t_model> &registers, Flags &flags, NSDictionary *value) {
registers.reset();
registers.ax() = [value[@"ax"] intValue];
registers.bx() = [value[@"bx"] intValue];
registers.cx() = [value[@"cx"] intValue];
@@ -324,6 +375,8 @@ void populate(InstructionSet::x86::Registers<t_model> &registers, Flags &flags,
);
}
// MARK: - Test runners; execution
template <InstructionSet::x86::Model t_model>
void apply_execution_test(
ExecutionSupport<t_model> &execution_support,
@@ -332,10 +385,24 @@ void apply_execution_test(
NSDictionary *test,
NSDictionary *metadata
) {
// NSLog(@"%@", test[@"hash"]);
if([test[@"hash"] isEqualToString:@"b16007339dbc7a5035f513f4d9ed56c3659ae040"]) {
printf("");
}
InstructionSet::x86::Decoder<t_model> decoder;
const auto data = bytes(test[@"bytes"]);
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();
const uint16_t flags_mask = metadata[@"flags-mask"] ? [metadata[@"flags-mask"] intValue] : 0xffff;
@@ -343,7 +410,7 @@ void apply_execution_test(
NSDictionary *const final_state = test[@"final"];
// Apply initial state.
Flags initial_flags;
Flags initial_flags(t_model);
for(NSArray<NSNumber *> *ram in initial_state[@"ram"]) {
execution_support.linear_memory.seed([ram[0] intValue], [ram[1] intValue]);
}
@@ -364,8 +431,6 @@ void apply_execution_test(
execution_support.registers.ip() += decoded.first;
do {
execution_support.flow_controller.begin_instruction();
// TODO: catch and process exceptions, which I think means better factoring
// re: PCCompatible/instruction set.
InstructionSet::x86::perform(
decoded.second,
execution_support,
@@ -373,16 +438,25 @@ void apply_execution_test(
);
} 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.
InstructionSet::x86::Registers<t_model> intended_registers;
InstructionSet::x86::Flags intended_flags;
InstructionSet::x86::Flags intended_flags(t_model);
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.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]) {
continue;
@@ -480,23 +554,26 @@ void apply_execution_test(
// Record a failure.
FailedExecution failure;
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];
if(!flagsEqual) {
Flags difference;
Flags difference(t_model);
difference.set((intended_flags.get() ^ execution_support.flags.get()) & flags_mask);
[reasons addObject:
[NSString stringWithFormat:@"flags differ; errors in %s",
difference.to_string().c_str()]];
[NSString stringWithFormat:@"flags differ; errors in %s due to final state %s",
difference.to_string().c_str(), execution_support.flags.to_string().c_str()]];
}
if(!registersEqual) {
NSMutableArray<NSString *> *registers = [[NSMutableArray alloc] init];
bool is_first = true;
#define Reg(x) \
if(intended_registers.x() != execution_support.registers.x()) \
if(intended_registers.x() != execution_support.registers.x()) { \
[registers addObject: \
[NSString stringWithFormat: \
@#x" is %04x rather than %04x", execution_support.registers.x(), intended_registers.x()]];
@#x" is %04x %@ %04x", execution_support.registers.x(), is_first ? @"but should have been" : @"not", intended_registers.x()]]; \
is_first = false; \
}
Reg(ax);
Reg(cx);
@@ -528,12 +605,12 @@ void apply_execution_test(
template <InstructionSet::x86::Model t_model>
void test_execution(const char *const 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> permitted_failures;
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();
// Determine the metadata key.
@@ -548,14 +625,12 @@ void test_execution(const char *const home) {
test_metadata[@"reg"][[NSString stringWithFormat:@"%c", [name characterAtIndex:first_dot.location+1]]];
}
// int index = 0;
for(NSDictionary *test in testsInFile(file)) {
for(NSDictionary *test in tests_in_file(file)) {
apply_execution_test(*execution_support, execution_failures, permitted_failures, test, test_metadata);
// ++index;
}
if (execution_failures.size() != failures_before) {
[failures addObject:file];
failures[file] = @([failures[file] intValue] + execution_failures.size() - failures_before);
}
}
@@ -569,28 +644,25 @@ void test_execution(const char *const home) {
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
@end
// MARK: - Test runners; decoding
@implementation i8088Tests
using Instruction = InstructionSet::x86::Instruction<InstructionSet::x86::InstructionType::Bits16>;
- (NSString *)
toString:(const std::pair<int, Instruction> &)instruction
offsetLength:(int)offsetLength
immediateLength:(int)immediateLength
{
const auto operation = to_string(instruction, InstructionSet::x86::Model::i8086, offsetLength, immediateLength);
template <InstructionSet::x86::Model model, InstructionSet::x86::InstructionType type>
NSString *to_string(
const std::pair<int, InstructionSet::x86::Instruction<type>> &instruction,
int offsetLength,
int immediateLength
) {
const auto operation = to_string(instruction, model, offsetLength, immediateLength);
return [[NSString stringWithUTF8String:operation.c_str()]
stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
}
- (bool)applyDecodingTest:(NSDictionary *)test file:(NSString *)file assert:(BOOL)assert {
InstructionSet::x86::Decoder<InstructionSet::x86::Model::i8086> decoder;
template <InstructionSet::x86::Model model>
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.
const auto data = bytes(test[@"bytes"]);
@@ -603,7 +675,10 @@ using Instruction = InstructionSet::x86::Instruction<InstructionSet::x86::Instru
};
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) {
XCTAssert(
sizeMatched,
@@ -622,12 +697,12 @@ using Instruction = InstructionSet::x86::Instruction<InstructionSet::x86::Instru
// The decoder doesn't preserve the original offset length, which makes no functional difference but
// does affect the way that offsets are printed in the test set.
NSSet<NSString *> *decodings = [NSSet setWithObjects:
[self toString:decoded offsetLength:4 immediateLength:4],
[self toString:decoded offsetLength:2 immediateLength:4],
[self toString:decoded offsetLength:0 immediateLength:4],
[self toString:decoded offsetLength:4 immediateLength:2],
[self toString:decoded offsetLength:2 immediateLength:2],
[self toString:decoded offsetLength:0 immediateLength:2],
to_string<model>(decoded, 4, 4),
to_string<model>(decoded, 2, 4),
to_string<model>(decoded, 0, 4),
to_string<model>(decoded, 4, 2),
to_string<model>(decoded, 2, 2),
to_string<model>(decoded, 0, 2),
nil];
auto compare_decoding = [&](NSString *name) -> bool {
@@ -672,36 +747,53 @@ using Instruction = InstructionSet::x86::Instruction<InstructionSet::x86::Instru
return isEqual;
}
- (void)printFailures:(NSArray<NSString *> *)failures {
NSLog(
@"%ld failures out of %ld tests: %@",
failures.count,
testFiles(TestSuiteHome8088).count,
[failures sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)]);
}
- (void)testDecoding {
template <InstructionSet::x86::Model model>
void test_decoding(const char *home) {
NSMutableArray<NSString *> *failures = [[NSMutableArray alloc] init];
for(NSString *file in testFiles(TestSuiteHome8088)) @autoreleasepool {
for(NSDictionary *test in testsInFile(file)) {
for(NSString *file in test_files(home)) @autoreleasepool {
for(NSDictionary *test in tests_in_file(file)) {
// 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];
// Attempt a second decoding, to provide a debugger hook.
[self applyDecodingTest:test file:file assert:NO];
apply_decoding_test<model>(test, file, NO);
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 {
test_execution<InstructionSet::x86::Model::i8086>(TestSuiteHome8088);
}
// MARK: - 80286
- (void)testDecoding80286 {
test_decoding<InstructionSet::x86::Model::i80286>(TestSuiteHome80286);
}
- (void)testExecution80286 {
test_execution<InstructionSet::x86::Model::i80286>(TestSuiteHome80286);
}