mirror of
https://github.com/TomHarte/CLK.git
synced 2025-08-07 23:25:00 +00:00
Compare commits
46 Commits
master
...
286Decodin
Author | SHA1 | Date | |
---|---|---|---|
|
8e5bbbbc71 | ||
|
615ebaf711 | ||
|
0882d2b7ce | ||
|
900195efac | ||
|
b58b962ccf | ||
|
5255499445 | ||
|
d9a2be4250 | ||
|
256e14a8a6 | ||
|
1ab26d4a2f | ||
|
91b2c751af | ||
|
edf7617d1e | ||
|
32666d304f | ||
|
b65f7b4a6a | ||
|
7c4df23c1c | ||
|
a8e60163e1 | ||
|
02ec1b5da6 | ||
|
a9a6aba862 | ||
|
03c6a60f68 | ||
|
8ab688687e | ||
|
bdec32722e | ||
|
ad50e5c754 | ||
|
9c48e44e9e | ||
|
76284eb462 | ||
|
0745c5128a | ||
|
01fbe2d3de | ||
|
9e14c22259 | ||
|
dff0111cd5 | ||
|
e7452b0ea1 | ||
|
61a0f892c4 | ||
|
4ceab01ed4 | ||
|
9908969eea | ||
|
19a78ef1ac | ||
|
4785c1ae84 | ||
|
ef03841efa | ||
|
4747a70ce7 | ||
|
cd986cc2dc | ||
|
c29d5ca4a8 | ||
|
56b49011d6 | ||
|
48c55211e6 | ||
|
72f68f3b0b | ||
|
7b6dddc994 | ||
|
51fbe4e8c5 | ||
|
c148d9ee6c | ||
|
9dfe59a104 | ||
|
b6aae65afd | ||
|
9fed93a771 |
@@ -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.
|
||||
|
@@ -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>;
|
||||
|
@@ -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 {
|
||||
|
@@ -82,7 +82,7 @@ constexpr bool posts_next_ip(const Vector vector) {
|
||||
case SingleStep:
|
||||
case Breakpoint:
|
||||
case Overflow:
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -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_{};
|
||||
};
|
||||
|
||||
}
|
||||
|
@@ -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.
|
||||
|
@@ -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);
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
||||
|
@@ -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);
|
||||
}
|
||||
|
||||
|
@@ -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));
|
||||
}
|
||||
|
||||
|
@@ -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;
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -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,
|
||||
|
@@ -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>
|
||||
|
@@ -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_]);
|
||||
}
|
||||
|
||||
|
@@ -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),
|
||||
|
@@ -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.
|
||||
|
||||
|
@@ -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> ®isters, PCCompatible::Segments<t_model, LinearMemory<t_model>> &segments) :
|
||||
FlowController(
|
||||
InstructionSet::x86::Registers<t_model> ®isters,
|
||||
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> ®isters_;
|
||||
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> ®isters, 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> ®isters, 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);
|
||||
}
|
||||
|
Reference in New Issue
Block a user