mirror of
https://github.com/TomHarte/CLK.git
synced 2025-08-08 14:25:05 +00:00
Compare commits
51 Commits
master
...
286Decodin
Author | SHA1 | Date | |
---|---|---|---|
|
54ff2fa01f | ||
|
03c70b49ba | ||
|
4b2d8e13d1 | ||
|
a0c50f0521 | ||
|
b15a865c88 | ||
|
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 {
|
} else {
|
||||||
immediate(Operation::PUSH, DataSize::Byte);
|
immediate(Operation::PUSH, DataSize::Byte);
|
||||||
operation_size_ = data_size_;
|
operation_size_ = data_size_;
|
||||||
|
sign_extend_operand_ = true;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 0x6b:
|
case 0x6b:
|
||||||
@@ -736,9 +737,8 @@ std::pair<int, typename Decoder<model>::InstructionT> Decoder<model>::decode(
|
|||||||
|
|
||||||
switch(reg) {
|
switch(reg) {
|
||||||
default:
|
default:
|
||||||
// case 1 is treated as another form of TEST on the 8086.
|
// case 1 is treated as another form of TEST through to at least the 80286.
|
||||||
// (and, I guess, the 80186?)
|
if constexpr (model >= Model::i80386) {
|
||||||
if constexpr (model >= Model::i80286) {
|
|
||||||
return undefined();
|
return undefined();
|
||||||
}
|
}
|
||||||
[[fallthrough]];
|
[[fallthrough]];
|
||||||
@@ -799,13 +799,20 @@ std::pair<int, typename Decoder<model>::InstructionT> Decoder<model>::decode(
|
|||||||
|
|
||||||
switch(reg) {
|
switch(reg) {
|
||||||
default:
|
default:
|
||||||
if constexpr (model == Model::i8086) {
|
switch(model) {
|
||||||
if(source_ == Source::eCX) {
|
case Model::i8086:
|
||||||
set(Operation::SETMOC);
|
if(source_ == Source::eCX) {
|
||||||
} else {
|
set(Operation::SETMOC);
|
||||||
set(Operation::SETMO);
|
} else {
|
||||||
}
|
set(Operation::SETMO);
|
||||||
} else {
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Model::i80286:
|
||||||
|
set(Operation::SAL);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
return undefined();
|
return undefined();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -865,6 +872,13 @@ std::pair<int, typename Decoder<model>::InstructionT> Decoder<model>::decode(
|
|||||||
source_ = Source::Immediate;
|
source_ = Source::Immediate;
|
||||||
destination_ = memreg;
|
destination_ = memreg;
|
||||||
operand_size_ = operation_size_;
|
operand_size_ = operation_size_;
|
||||||
|
|
||||||
|
// This form requires that the reg field be blank
|
||||||
|
if constexpr (model >= Model::i80286) {
|
||||||
|
if(reg != 0) {
|
||||||
|
return undefined();
|
||||||
|
}
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case ModRegRMFormat::MemRegADD_to_CMP:
|
case ModRegRMFormat::MemRegADD_to_CMP:
|
||||||
@@ -1017,8 +1031,13 @@ std::pair<int, typename Decoder<model>::InstructionT> Decoder<model>::decode(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Provide a genuine measure of further bytes required.
|
// Provide a genuine measure of further bytes required, or post a bad instruction
|
||||||
return std::make_pair(-(outstanding_bytes - bytes_to_consume), InstructionT());
|
// if the length limit has been breached.
|
||||||
|
if(consumed_ != max_instruction_length) {
|
||||||
|
return std::make_pair(-(outstanding_bytes - bytes_to_consume), InstructionT());
|
||||||
|
} else {
|
||||||
|
return overlong();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1037,6 +1056,18 @@ std::pair<int, typename Decoder<model>::InstructionT> Decoder<model>::decode(
|
|||||||
// operations would unlock an extra bit of storage for a net gain of 239 extra operation types and thereby
|
// operations would unlock an extra bit of storage for a net gain of 239 extra operation types and thereby
|
||||||
// alleviating any concerns over whether there'll be space to handle MMX, floating point extensions, etc.
|
// alleviating any concerns over whether there'll be space to handle MMX, floating point extensions, etc.
|
||||||
|
|
||||||
|
if constexpr (model >= Model::i80286) {
|
||||||
|
if(operation_ == Operation::BOUND && !is_address(source_)) {
|
||||||
|
return undefined();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(
|
||||||
|
(operation_ == Operation::JMPfar || operation_ == Operation::CALLfar) &&
|
||||||
|
destination_ < Source::DirectAddress
|
||||||
|
) {
|
||||||
|
return undefined();
|
||||||
|
}
|
||||||
|
|
||||||
const auto result = std::make_pair(
|
const auto result = std::make_pair(
|
||||||
consumed_,
|
consumed_,
|
||||||
InstructionT(
|
InstructionT(
|
||||||
@@ -1058,14 +1089,7 @@ std::pair<int, typename Decoder<model>::InstructionT> Decoder<model>::decode(
|
|||||||
|
|
||||||
// Check for a too-long instruction.
|
// Check for a too-long instruction.
|
||||||
if(consumed_ == max_instruction_length) {
|
if(consumed_ == max_instruction_length) {
|
||||||
std::pair<int, InstructionT> result;
|
return overlong();
|
||||||
if(max_instruction_length == 65536) {
|
|
||||||
result = std::make_pair(consumed_, InstructionT(Operation::NOP));
|
|
||||||
} else {
|
|
||||||
result = std::make_pair(consumed_, InstructionT());
|
|
||||||
}
|
|
||||||
reset_parsing();
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// i.e. not done yet.
|
// i.e. not done yet.
|
||||||
|
@@ -349,6 +349,7 @@ private:
|
|||||||
phase_ = Phase::DisplacementOrOperand;
|
phase_ = Phase::DisplacementOrOperand;
|
||||||
displacement_size_ = DataSize::Word;
|
displacement_size_ = DataSize::Word;
|
||||||
operand_size_ = DataSize::Byte;
|
operand_size_ = DataSize::Byte;
|
||||||
|
operation_size_ = DataSize::Byte;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets up the operation size, oncoming phase and modregrm format for a member of the shift group (i.e. 'group 2').
|
/// Sets up the operation size, oncoming phase and modregrm format for a member of the shift group (i.e. 'group 2').
|
||||||
@@ -367,6 +368,16 @@ private:
|
|||||||
reset_parsing();
|
reset_parsing();
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::pair<int, typename Decoder<model>::InstructionT> overlong() {
|
||||||
|
const auto consumed = consumed_;
|
||||||
|
reset_parsing();
|
||||||
|
if(consumed == 65536) {
|
||||||
|
return std::make_pair(consumed, InstructionT(Operation::NOP));
|
||||||
|
} else {
|
||||||
|
return std::make_pair(consumed, InstructionT(ExceptionCode()));
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
extern template class InstructionSet::x86::Decoder<InstructionSet::x86::Model::i8086>;
|
extern template class InstructionSet::x86::Decoder<InstructionSet::x86::Model::i8086>;
|
||||||
|
@@ -81,6 +81,12 @@ struct SegmentDescriptor {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Test for bounds; end && end < begin captures instances where end is
|
||||||
|
// both out of bounds and beyond the range of AddressT.
|
||||||
|
if(begin < bounds_.begin || end > bounds_.end || (end && end < begin)) {
|
||||||
|
throw_exception();
|
||||||
|
}
|
||||||
|
|
||||||
// Tested at loading (?): present(), privilege_level().
|
// Tested at loading (?): present(), privilege_level().
|
||||||
|
|
||||||
if(type == AccessType::Read && executable() && !readable()) {
|
if(type == AccessType::Read && executable() && !readable()) {
|
||||||
@@ -90,10 +96,6 @@ struct SegmentDescriptor {
|
|||||||
if(type == AccessType::Write && !executable() && !writeable()) {
|
if(type == AccessType::Write && !executable() && !writeable()) {
|
||||||
throw_exception();
|
throw_exception();
|
||||||
}
|
}
|
||||||
|
|
||||||
if(begin < bounds_.begin || end >= bounds_.end) {
|
|
||||||
throw_exception();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void validate_as(const Source segment) const {
|
void validate_as(const Source segment) const {
|
||||||
|
@@ -8,6 +8,9 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <cassert>
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
namespace InstructionSet::x86 {
|
namespace InstructionSet::x86 {
|
||||||
|
|
||||||
enum class Vector: uint8_t {
|
enum class Vector: uint8_t {
|
||||||
@@ -82,7 +85,7 @@ constexpr bool posts_next_ip(const Vector vector) {
|
|||||||
case SingleStep:
|
case SingleStep:
|
||||||
case Breakpoint:
|
case Breakpoint:
|
||||||
case Overflow:
|
case Overflow:
|
||||||
return false;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -108,6 +111,9 @@ struct ExceptionCode {
|
|||||||
// 1 => trigger was external to program code;
|
// 1 => trigger was external to program code;
|
||||||
// 0 => trigger was caused by the instruction described by the CS:IP that is on the stack.
|
// 0 => trigger was caused by the instruction described by the CS:IP that is on the stack.
|
||||||
|
|
||||||
|
ExceptionCode(const uint16_t value) :
|
||||||
|
value_(value) {}
|
||||||
|
|
||||||
operator uint16_t() const {
|
operator uint16_t() const {
|
||||||
return value_;
|
return value_;
|
||||||
}
|
}
|
||||||
|
@@ -81,6 +81,18 @@ class Flags {
|
|||||||
public:
|
public:
|
||||||
using FlagT = uint32_t;
|
using FlagT = uint32_t;
|
||||||
|
|
||||||
|
Flags(const Model model) {
|
||||||
|
switch(model) {
|
||||||
|
case Model::i8086:
|
||||||
|
case Model::i80186:
|
||||||
|
forced_set_ = 0xf002;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
forced_set_ = 0x0002;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Flag getters.
|
// Flag getters.
|
||||||
template <Flag flag_v> bool flag() const {
|
template <Flag flag_v> bool flag() const {
|
||||||
switch(flag_v) {
|
switch(flag_v) {
|
||||||
@@ -177,7 +189,7 @@ public:
|
|||||||
|
|
||||||
uint16_t get() const {
|
uint16_t get() const {
|
||||||
return
|
return
|
||||||
0xf002 |
|
forced_set_ |
|
||||||
|
|
||||||
(flag<Flag::Carry>() ? FlagValue::Carry : 0) |
|
(flag<Flag::Carry>() ? FlagValue::Carry : 0) |
|
||||||
(flag<Flag::AuxiliaryCarry>() ? FlagValue::AuxiliaryCarry : 0) |
|
(flag<Flag::AuxiliaryCarry>() ? FlagValue::AuxiliaryCarry : 0) |
|
||||||
@@ -232,6 +244,9 @@ private:
|
|||||||
|
|
||||||
// Odd number of bits => set; even => unset.
|
// Odd number of bits => set; even => unset.
|
||||||
uint32_t parity_{};
|
uint32_t parity_{};
|
||||||
|
|
||||||
|
// Model specific stuff: bits that are always set, regardless of other state.
|
||||||
|
uint16_t forced_set_{};
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -128,7 +128,7 @@ void mul(
|
|||||||
}
|
}
|
||||||
|
|
||||||
template <typename IntT, typename ContextT>
|
template <typename IntT, typename ContextT>
|
||||||
void imul(
|
void imul_double(
|
||||||
modify_t<IntT> destination_high,
|
modify_t<IntT> destination_high,
|
||||||
modify_t<IntT> destination_low,
|
modify_t<IntT> destination_low,
|
||||||
read_t<IntT> source,
|
read_t<IntT> source,
|
||||||
@@ -167,6 +167,22 @@ void imul(
|
|||||||
context.flags.template set_from<Flag::Overflow, Flag::Carry>(destination_high != sign_extension);
|
context.flags.template set_from<Flag::Overflow, Flag::Carry>(destination_high != sign_extension);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <typename IntT, typename ContextT>
|
||||||
|
void imul_single(
|
||||||
|
write_t<IntT> destination,
|
||||||
|
read_t<IntT> source1,
|
||||||
|
read_t<IntT> source2,
|
||||||
|
ContextT &context
|
||||||
|
) {
|
||||||
|
using sIntT = typename std::make_signed<IntT>::type;
|
||||||
|
const auto top_part = IntT((sIntT(source1) * sIntT(source2)) >> (8 * sizeof(IntT)));
|
||||||
|
const auto result = IntT(sIntT(source1) * sIntT(source2));
|
||||||
|
destination = result;
|
||||||
|
|
||||||
|
const auto sign_extension = (result & Numeric::top_bit<IntT>()) ? IntT(~0) : 0;
|
||||||
|
context.flags.template set_from<Flag::Overflow, Flag::Carry>(top_part != sign_extension);
|
||||||
|
}
|
||||||
|
|
||||||
template <typename ContextT>
|
template <typename ContextT>
|
||||||
void divide_error(ContextT &context) {
|
void divide_error(ContextT &context) {
|
||||||
// 8086-style: just segue directly to the interrupt.
|
// 8086-style: just segue directly to the interrupt.
|
||||||
|
@@ -22,10 +22,18 @@ void aaas(
|
|||||||
) {
|
) {
|
||||||
if((ax.halves.low & 0x0f) > 9 || context.flags.template flag<Flag::AuxiliaryCarry>()) {
|
if((ax.halves.low & 0x0f) > 9 || context.flags.template flag<Flag::AuxiliaryCarry>()) {
|
||||||
if constexpr (add) {
|
if constexpr (add) {
|
||||||
ax.halves.low += 6;
|
if constexpr (ContextT::model <= Model::i80186) {
|
||||||
|
ax.halves.low += 6;
|
||||||
|
} else {
|
||||||
|
ax.full += 6;
|
||||||
|
}
|
||||||
++ax.halves.high;
|
++ax.halves.high;
|
||||||
} else {
|
} else {
|
||||||
ax.halves.low -= 6;
|
if constexpr (ContextT::model <= Model::i80186) {
|
||||||
|
ax.halves.low -= 6;
|
||||||
|
} else {
|
||||||
|
ax.full -= 6;
|
||||||
|
}
|
||||||
--ax.halves.high;
|
--ax.halves.high;
|
||||||
}
|
}
|
||||||
context.flags.template set_from<Flag::Carry, Flag::AuxiliaryCarry>(1);
|
context.flags.template set_from<Flag::Carry, Flag::AuxiliaryCarry>(1);
|
||||||
@@ -96,18 +104,27 @@ void daas(
|
|||||||
ContextT &context
|
ContextT &context
|
||||||
) {
|
) {
|
||||||
bool top_exceeded_threshold;
|
bool top_exceeded_threshold;
|
||||||
if constexpr (ContextT::model == Model::i8086) {
|
constexpr bool is_8086 = ContextT::model == Model::i8086;
|
||||||
|
if constexpr (is_8086) {
|
||||||
top_exceeded_threshold = al > (context.flags.template flag<Flag::AuxiliaryCarry>() ? 0x9f : 0x99);
|
top_exceeded_threshold = al > (context.flags.template flag<Flag::AuxiliaryCarry>() ? 0x9f : 0x99);
|
||||||
} else {
|
} else {
|
||||||
top_exceeded_threshold = al > 0x99;
|
top_exceeded_threshold = al > 0x99;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const auto initial_cf = context.flags.template flag<Flag::Carry>();
|
||||||
if((al & 0x0f) > 0x09 || context.flags.template flag<Flag::AuxiliaryCarry>()) {
|
if((al & 0x0f) > 0x09 || context.flags.template flag<Flag::AuxiliaryCarry>()) {
|
||||||
if constexpr (add) al += 0x06; else al -= 0x06;
|
const auto prior_al = al;
|
||||||
|
if constexpr (add) {
|
||||||
|
al += 0x06;
|
||||||
|
if(!is_8086 && al < prior_al) context.flags.template set_from<Flag::Carry>(1);
|
||||||
|
} else {
|
||||||
|
al -= 0x06;
|
||||||
|
if(!is_8086 && al > prior_al) context.flags.template set_from<Flag::Carry>(1);
|
||||||
|
}
|
||||||
context.flags.template set_from<Flag::AuxiliaryCarry>(1);
|
context.flags.template set_from<Flag::AuxiliaryCarry>(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(top_exceeded_threshold || context.flags.template flag<Flag::Carry>()) {
|
if(top_exceeded_threshold || initial_cf) {
|
||||||
if constexpr (add) al += 0x60; else al -= 0x60;
|
if constexpr (add) al += 0x60; else al -= 0x60;
|
||||||
context.flags.template set_from<Flag::Carry>(1);
|
context.flags.template set_from<Flag::Carry>(1);
|
||||||
}
|
}
|
||||||
|
@@ -263,18 +263,17 @@ void into(
|
|||||||
template <typename IntT, typename AddressT, typename InstructionT, typename ContextT>
|
template <typename IntT, typename AddressT, typename InstructionT, typename ContextT>
|
||||||
void bound(
|
void bound(
|
||||||
const InstructionT &instruction,
|
const InstructionT &instruction,
|
||||||
read_t<AddressT> destination,
|
read_t<IntT> destination,
|
||||||
read_t<AddressT> source,
|
read_t<AddressT> source,
|
||||||
ContextT &context
|
ContextT &context
|
||||||
) {
|
) {
|
||||||
using sIntT = typename std::make_signed<IntT>::type;
|
using sIntT = typename std::make_signed<IntT>::type;
|
||||||
|
|
||||||
const auto source_segment = instruction.data_segment();
|
const auto source_segment = instruction.data_segment();
|
||||||
context.memory.preauthorise_read(source_segment, source, 2*sizeof(IntT));
|
|
||||||
const auto lower_bound =
|
const auto lower_bound =
|
||||||
sIntT(context.memory.template access<IntT, AccessType::PreauthorisedRead>(source_segment, source));
|
sIntT(context.memory.template access<IntT, AccessType::Read>(source_segment, source));
|
||||||
const auto upper_bound =
|
const auto upper_bound =
|
||||||
sIntT(context.memory.template access<IntT, AccessType::PreauthorisedRead>(source_segment, IntT(source + 2)));
|
sIntT(context.memory.template access<IntT, AccessType::Read>(source_segment, IntT(source + 2)));
|
||||||
|
|
||||||
if(sIntT(destination) < lower_bound || sIntT(destination) > upper_bound) {
|
if(sIntT(destination) < lower_bound || sIntT(destination) > upper_bound) {
|
||||||
constexpr auto exception = Exception::exception<Vector::BoundRangeExceeded>();
|
constexpr auto exception = Exception::exception<Vector::BoundRangeExceeded>();
|
||||||
|
@@ -40,12 +40,11 @@ void ld(
|
|||||||
uint16_t source_address = uint16_t(address<uint16_t, AccessType::Read>(instruction, pointer, context));
|
uint16_t source_address = uint16_t(address<uint16_t, AccessType::Read>(instruction, pointer, context));
|
||||||
const Source source_segment = instruction.data_segment();
|
const Source source_segment = instruction.data_segment();
|
||||||
|
|
||||||
context.memory.preauthorise_read(source_segment, source_address, 4);
|
|
||||||
const auto offset =
|
const auto offset =
|
||||||
context.memory.template access<uint16_t, AccessType::PreauthorisedRead>(source_segment, source_address);
|
context.memory.template access<uint16_t, AccessType::Read>(source_segment, source_address);
|
||||||
source_address += 2;
|
source_address += 2;
|
||||||
const auto segment =
|
const auto segment =
|
||||||
context.memory.template access<uint16_t, AccessType::PreauthorisedRead>(source_segment, source_address);
|
context.memory.template access<uint16_t, AccessType::Read>(source_segment, source_address);
|
||||||
|
|
||||||
context.segments.preauthorise(selector, segment);
|
context.segments.preauthorise(selector, segment);
|
||||||
destination = offset;
|
destination = offset;
|
||||||
|
@@ -128,6 +128,12 @@ template <
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const auto source_address = [&]() -> AddressT {
|
||||||
|
return AddressT(
|
||||||
|
address<AddressT, AccessType::Read>(instruction, instruction.source(), context)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
// Some instructions use a pair of registers as an extended accumulator — DX:AX or EDX:EAX.
|
// Some instructions use a pair of registers as an extended accumulator — DX:AX or EDX:EAX.
|
||||||
// The two following return the high and low parts of that pair; they also work in Byte mode to return AH:AL,
|
// The two following return the high and low parts of that pair; they also work in Byte mode to return AH:AL,
|
||||||
// i.e. AX split into high and low parts.
|
// i.e. AX split into high and low parts.
|
||||||
@@ -190,8 +196,14 @@ template <
|
|||||||
case Operation::NOP: return;
|
case Operation::NOP: return;
|
||||||
|
|
||||||
case Operation::Invalid:
|
case Operation::Invalid:
|
||||||
|
// TODO: ask whether the issue was overlong. If so then
|
||||||
|
// get an exception code and do a GPF.
|
||||||
if constexpr (!uses_8086_exceptions(ContextT::model)) {
|
if constexpr (!uses_8086_exceptions(ContextT::model)) {
|
||||||
throw Exception::exception<Vector::InvalidOpcode>();
|
if(instruction.invalid_is_gpf()) {
|
||||||
|
throw Exception::exception<Vector::GeneralProtectionFault>(instruction.gpf_exception_code());
|
||||||
|
} else {
|
||||||
|
throw Exception::exception<Vector::InvalidOpcode>();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@@ -241,18 +253,21 @@ template <
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
case Operation::MUL: Primitive::mul<IntT>(pair_high(), pair_low(), source_r(), context); return;
|
case Operation::MUL: Primitive::mul<IntT>(pair_high(), pair_low(), source_r(), context); return;
|
||||||
case Operation::IMUL_1: Primitive::imul<IntT>(pair_high(), pair_low(), source_r(), context); return;
|
case Operation::IMUL_1:
|
||||||
|
Primitive::imul_double<IntT>(pair_high(), pair_low(), source_r(), context);
|
||||||
|
return;
|
||||||
|
case Operation::IMUL_3:
|
||||||
|
Primitive::imul_single<IntT>(destination_w(), source_r(), IntT(instruction.operand()), context);
|
||||||
|
return;
|
||||||
case Operation::DIV: Primitive::div<IntT>(pair_high(), pair_low(), source_r(), context); return;
|
case Operation::DIV: Primitive::div<IntT>(pair_high(), pair_low(), source_r(), context); return;
|
||||||
case Operation::IDIV: Primitive::idiv<false, IntT>(pair_high(), pair_low(), source_r(), context); return;
|
case Operation::IDIV: Primitive::idiv<false, IntT>(pair_high(), pair_low(), source_r(), context); return;
|
||||||
case Operation::IDIV_REP:
|
case Operation::IDIV_REP:
|
||||||
if constexpr (ContextT::model == Model::i8086) {
|
if constexpr (ContextT::model < Model::i80286) {
|
||||||
Primitive::idiv<true, IntT>(pair_high(), pair_low(), source_r(), context);
|
Primitive::idiv<true, IntT>(pair_high(), pair_low(), source_r(), context);
|
||||||
break;
|
break;
|
||||||
} else {
|
} else {
|
||||||
static_assert(int(Operation::IDIV_REP) == int(Operation::LEAVE));
|
static_assert(int(Operation::IDIV_REP) == int(Operation::LEAVE));
|
||||||
if constexpr (std::is_same_v<IntT, uint16_t> || std::is_same_v<IntT, uint32_t>) {
|
Primitive::leave<IntT>(context);
|
||||||
Primitive::leave<IntT>(context);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@@ -431,7 +446,7 @@ template <
|
|||||||
break;
|
break;
|
||||||
} else {
|
} else {
|
||||||
static_assert(int(Operation::SETMOC) == int(Operation::BOUND));
|
static_assert(int(Operation::SETMOC) == int(Operation::BOUND));
|
||||||
Primitive::bound<IntT, AddressT>(instruction, destination_r(), source_r(), context);
|
Primitive::bound<IntT, AddressT>(instruction, destination_r(), source_address(), context);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@@ -453,9 +468,14 @@ template <
|
|||||||
}
|
}
|
||||||
} break;
|
} break;
|
||||||
case Operation::PUSH:
|
case Operation::PUSH:
|
||||||
Primitive::push<IntT, false>(source_rmw(), context); // PUSH SP modifies SP before pushing it;
|
if constexpr (ContextT::model >= Model::i80286) {
|
||||||
// hence PUSH is sometimes read-modify-write.
|
Primitive::push<IntT, false>(source_r(), context);
|
||||||
break;
|
} else {
|
||||||
|
Primitive::push<IntT, false>(source_rmw(), context); // Prior to the 286, PUSH SP modifies SP
|
||||||
|
// before pushing it; hence PUSH is
|
||||||
|
// sometimes read-modify-write.
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
|
||||||
case Operation::POPF:
|
case Operation::POPF:
|
||||||
if constexpr (std::is_same_v<IntT, uint16_t> || std::is_same_v<IntT, uint32_t>) {
|
if constexpr (std::is_same_v<IntT, uint16_t> || std::is_same_v<IntT, uint32_t>) {
|
||||||
@@ -570,7 +590,6 @@ template <
|
|||||||
// LSL
|
// LSL
|
||||||
// LTR
|
// LTR
|
||||||
// STR
|
// STR
|
||||||
// IMUL_3
|
|
||||||
// LOADALL
|
// LOADALL
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -592,7 +611,7 @@ void perform(
|
|||||||
const Instruction<type> &instruction,
|
const Instruction<type> &instruction,
|
||||||
ContextT &context
|
ContextT &context
|
||||||
) {
|
) {
|
||||||
const auto size = [](DataSize operation_size, AddressSize address_size) constexpr -> int {
|
const auto size = [](const DataSize operation_size, const AddressSize address_size) constexpr -> int {
|
||||||
return int(operation_size) + (int(address_size) << 2);
|
return int(operation_size) + (int(address_size) << 2);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -647,7 +666,8 @@ void perform(
|
|||||||
|
|
||||||
// This is reachable only if the data and address size combination in use isn't available
|
// This is reachable only if the data and address size combination in use isn't available
|
||||||
// on the processor model nominated.
|
// on the processor model nominated.
|
||||||
assert(false);}
|
assert(false);
|
||||||
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// Public function; just indirects into a trampoline into a version of perform templated on data and address size.
|
// Public function; just indirects into a trampoline into a version of perform templated on data and address size.
|
||||||
@@ -678,6 +698,7 @@ void perform(
|
|||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
} catch (const InstructionSet::x86::Exception exception) {
|
} catch (const InstructionSet::x86::Exception exception) {
|
||||||
|
context.flow_controller.cancel_repetition();
|
||||||
fault(exception, context, source_ip);
|
fault(exception, context, source_ip);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -51,8 +51,28 @@ void cmps(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
IntT lhs = context.memory.template access<IntT, AccessType::Read>(instruction.data_segment(), eSI);
|
IntT lhs, rhs;
|
||||||
const IntT rhs = context.memory.template access<IntT, AccessType::Read>(Source::ES, eDI);
|
if constexpr (!uses_8086_exceptions(ContextT::model)) {
|
||||||
|
try {
|
||||||
|
rhs = context.memory.template access<IntT, AccessType::Read>(Source::ES, eDI);
|
||||||
|
} catch (const Exception &e) {
|
||||||
|
eDI += context.flags.template direction<AddressT>() * sizeof(IntT);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
lhs = context.memory.template access<IntT, AccessType::Read>(instruction.data_segment(), eSI);
|
||||||
|
} catch (const Exception &e) {
|
||||||
|
eDI += context.flags.template direction<AddressT>() * sizeof(IntT);
|
||||||
|
eSI += context.flags.template direction<AddressT>() * sizeof(IntT);
|
||||||
|
repeat<AddressT, repetition>(eCX, context);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
rhs = context.memory.template access<IntT, AccessType::Read>(Source::ES, eDI);
|
||||||
|
lhs = context.memory.template access<IntT, AccessType::Read>(instruction.data_segment(), eSI);
|
||||||
|
}
|
||||||
eSI += context.flags.template direction<AddressT>() * sizeof(IntT);
|
eSI += context.flags.template direction<AddressT>() * sizeof(IntT);
|
||||||
eDI += context.flags.template direction<AddressT>() * sizeof(IntT);
|
eDI += context.flags.template direction<AddressT>() * sizeof(IntT);
|
||||||
|
|
||||||
@@ -72,11 +92,22 @@ void scas(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const IntT rhs = context.memory.template access<IntT, AccessType::Read>(Source::ES, eDI);
|
IntT rhs;
|
||||||
|
if(!uses_8086_exceptions(ContextT::model)) {
|
||||||
|
try {
|
||||||
|
rhs = context.memory.template access<IntT, AccessType::Read>(Source::ES, eDI);
|
||||||
|
} catch (const Exception &e) {
|
||||||
|
eDI += context.flags.template direction<AddressT>() * sizeof(IntT);
|
||||||
|
repeat<AddressT, repetition>(eCX, context);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
rhs = context.memory.template access<IntT, AccessType::Read>(Source::ES, eDI);
|
||||||
|
}
|
||||||
|
|
||||||
eDI += context.flags.template direction<AddressT>() * sizeof(IntT);
|
eDI += context.flags.template direction<AddressT>() * sizeof(IntT);
|
||||||
|
|
||||||
Primitive::sub<false, AccessType::Read, IntT>(eAX, rhs, context);
|
Primitive::sub<false, AccessType::Read, IntT>(eAX, rhs, context);
|
||||||
|
|
||||||
repeat<AddressT, repetition>(eCX, context);
|
repeat<AddressT, repetition>(eCX, context);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -92,9 +123,19 @@ void lods(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
eAX = context.memory.template access<IntT, AccessType::Read>(instruction.data_segment(), eSI);
|
if(!uses_8086_exceptions(ContextT::model)) {
|
||||||
eSI += context.flags.template direction<AddressT>() * sizeof(IntT);
|
try {
|
||||||
|
eAX = context.memory.template access<IntT, AccessType::Read>(instruction.data_segment(), eSI);
|
||||||
|
} catch (const Exception &e) {
|
||||||
|
eSI += context.flags.template direction<AddressT>() * sizeof(IntT);
|
||||||
|
repeat<AddressT, repetition>(eCX, context);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
eAX = context.memory.template access<IntT, AccessType::Read>(instruction.data_segment(), eSI);
|
||||||
|
}
|
||||||
|
|
||||||
|
eSI += context.flags.template direction<AddressT>() * sizeof(IntT);
|
||||||
repeat<AddressT, repetition>(eCX, context);
|
repeat<AddressT, repetition>(eCX, context);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,12 +151,33 @@ void movs(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
context.memory.template access<IntT, AccessType::Write>(Source::ES, eDI) =
|
if constexpr (!uses_8086_exceptions(ContextT::model)) {
|
||||||
context.memory.template access<IntT, AccessType::Read>(instruction.data_segment(), eSI);
|
IntT temp;
|
||||||
|
|
||||||
|
try {
|
||||||
|
temp = context.memory.template access<IntT, AccessType::Read>(instruction.data_segment(), eSI);
|
||||||
|
} catch (const Exception &e) {
|
||||||
|
eSI += context.flags.template direction<AddressT>() * sizeof(IntT);
|
||||||
|
repeat<AddressT, repetition>(eCX, context);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
context.memory.template access<IntT, AccessType::Write>(Source::ES, eDI) = temp;
|
||||||
|
} catch (const Exception &e) {
|
||||||
|
eSI += context.flags.template direction<AddressT>() * sizeof(IntT);
|
||||||
|
eDI += context.flags.template direction<AddressT>() * sizeof(IntT);
|
||||||
|
repeat<AddressT, repetition>(eCX, context);
|
||||||
|
repeat<AddressT, repetition>(eCX, context);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
context.memory.template access<IntT, AccessType::Write>(Source::ES, eDI) =
|
||||||
|
context.memory.template access<IntT, AccessType::Read>(instruction.data_segment(), eSI);
|
||||||
|
}
|
||||||
|
|
||||||
eSI += context.flags.template direction<AddressT>() * sizeof(IntT);
|
eSI += context.flags.template direction<AddressT>() * sizeof(IntT);
|
||||||
eDI += context.flags.template direction<AddressT>() * sizeof(IntT);
|
eDI += context.flags.template direction<AddressT>() * sizeof(IntT);
|
||||||
|
|
||||||
repeat<AddressT, repetition>(eCX, context);
|
repeat<AddressT, repetition>(eCX, context);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -123,16 +185,33 @@ template <typename IntT, typename AddressT, Repetition repetition, typename Cont
|
|||||||
void stos(
|
void stos(
|
||||||
AddressT &eCX,
|
AddressT &eCX,
|
||||||
AddressT &eDI,
|
AddressT &eDI,
|
||||||
IntT &eAX,
|
const IntT eAX,
|
||||||
ContextT &context
|
ContextT &context
|
||||||
) {
|
) {
|
||||||
if(repetition_over<AddressT, repetition>(eCX)) {
|
if(repetition_over<AddressT, repetition>(eCX)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
context.memory.template access<IntT, AccessType::Write>(Source::ES, eDI) = eAX;
|
if constexpr (!uses_8086_exceptions(ContextT::model)) {
|
||||||
eDI += context.flags.template direction<AddressT>() * sizeof(IntT);
|
try {
|
||||||
|
context.memory.template access<IntT, AccessType::Write>(Source::ES, eDI) = eAX;
|
||||||
|
} catch (const Exception &e) {
|
||||||
|
// Empirical quirk of at least the 286: DI is adjusted even if the following access throws,
|
||||||
|
// and CX has been adjusted... twice?
|
||||||
|
//
|
||||||
|
// (yes: including even if CX has already hit zero)
|
||||||
|
if constexpr (ContextT::model == Model::i80286) {
|
||||||
|
eDI += context.flags.template direction<AddressT>() * sizeof(IntT);
|
||||||
|
repeat<AddressT, repetition>(eCX, context);
|
||||||
|
repeat<AddressT, repetition>(eCX, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
context.memory.template access<IntT, AccessType::Write>(Source::ES, eDI) = eAX;
|
||||||
|
}
|
||||||
|
eDI += context.flags.template direction<AddressT>() * sizeof(IntT);
|
||||||
repeat<AddressT, repetition>(eCX, context);
|
repeat<AddressT, repetition>(eCX, context);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -148,12 +227,26 @@ void outs(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
context.io.template out<IntT>(
|
// TODO: break down as two potentially-throwing steps.
|
||||||
port,
|
if(!uses_8086_exceptions(ContextT::model)) {
|
||||||
context.memory.template access<IntT, AccessType::Read>(instruction.data_segment(), eSI)
|
try {
|
||||||
);
|
context.io.template out<IntT>(
|
||||||
eSI += context.flags.template direction<AddressT>() * sizeof(IntT);
|
port,
|
||||||
|
context.memory.template access<IntT, AccessType::Read>(instruction.data_segment(), eSI)
|
||||||
|
);
|
||||||
|
} catch (const Exception &e) {
|
||||||
|
eSI += context.flags.template direction<AddressT>() * sizeof(IntT);
|
||||||
|
repeat<AddressT, repetition>(eCX, context);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
context.io.template out<IntT>(
|
||||||
|
port,
|
||||||
|
context.memory.template access<IntT, AccessType::Read>(instruction.data_segment(), eSI)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
eSI += context.flags.template direction<AddressT>() * sizeof(IntT);
|
||||||
repeat<AddressT, repetition>(eCX, context);
|
repeat<AddressT, repetition>(eCX, context);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -168,9 +261,22 @@ void ins(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
context.memory.template access<IntT, AccessType::Write>(Source::ES, eDI) = context.io.template in<IntT>(port);
|
// TODO: break down as two potentially-throwing steps.
|
||||||
eDI += context.flags.template direction<AddressT>() * sizeof(IntT);
|
if(!uses_8086_exceptions(ContextT::model)) {
|
||||||
|
try {
|
||||||
|
context.memory.template access<IntT, AccessType::Write>(Source::ES, eDI) =
|
||||||
|
context.io.template in<IntT>(port);
|
||||||
|
} catch (const Exception &e) {
|
||||||
|
eDI += context.flags.template direction<AddressT>() * sizeof(IntT);
|
||||||
|
repeat<AddressT, repetition>(eCX, context);
|
||||||
|
repeat<AddressT, repetition>(eCX, context);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
context.memory.template access<IntT, AccessType::Write>(Source::ES, eDI) = context.io.template in<IntT>(port);
|
||||||
|
}
|
||||||
|
|
||||||
|
eDI += context.flags.template direction<AddressT>() * sizeof(IntT);
|
||||||
repeat<AddressT, repetition>(eCX, context);
|
repeat<AddressT, repetition>(eCX, context);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -50,12 +50,18 @@ uint32_t address(
|
|||||||
if constexpr (instruction_type(ContextT::model) != InstructionType::Bits16) {
|
if constexpr (instruction_type(ContextT::model) != InstructionType::Bits16) {
|
||||||
address <<= pointer.scale();
|
address <<= pointer.scale();
|
||||||
}
|
}
|
||||||
|
// printf("%d + %d", address, instruction.offset());
|
||||||
address += instruction.offset();
|
address += instruction.offset();
|
||||||
|
|
||||||
if constexpr (source == Source::IndirectNoBase) {
|
if constexpr (source == Source::IndirectNoBase) {
|
||||||
|
// printf("\n");
|
||||||
return address;
|
return address;
|
||||||
}
|
}
|
||||||
return address + resolve<uint16_t, AccessType::Read>(instruction, pointer.base(), pointer, context);
|
|
||||||
|
const auto base = resolve<uint16_t, AccessType::Read>(instruction, pointer.base(), pointer, context);
|
||||||
|
// printf("+ %d\n", base);
|
||||||
|
address += base;
|
||||||
|
return address;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @returns a pointer to the contents of the register identified by the combination of @c IntT and @c Source if any;
|
/// @returns a pointer to the contents of the register identified by the combination of @c IntT and @c Source if any;
|
||||||
@@ -207,6 +213,10 @@ typename Accessor<IntT, access>::type resolve(
|
|||||||
// Do it and exit.
|
// Do it and exit.
|
||||||
//
|
//
|
||||||
// TODO: support 32-bit addresses.
|
// TODO: support 32-bit addresses.
|
||||||
|
|
||||||
|
//
|
||||||
|
// TODO: validate offset. If it's greater than the address size, fault?
|
||||||
|
//
|
||||||
return context.memory.template access<IntT, access>(instruction.data_segment(), uint16_t(target_address));
|
return context.memory.template access<IntT, access>(instruction.data_segment(), uint16_t(target_address));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -52,7 +52,9 @@ void rcl(
|
|||||||
const auto temp_count = count % (Numeric::bit_size<IntT>() + 1);
|
const auto temp_count = count % (Numeric::bit_size<IntT>() + 1);
|
||||||
auto carry = context.flags.template carry_bit<IntT>();
|
auto carry = context.flags.template carry_bit<IntT>();
|
||||||
switch(temp_count) {
|
switch(temp_count) {
|
||||||
case 0: break;
|
case 0:
|
||||||
|
if(!count) return;
|
||||||
|
break;
|
||||||
case Numeric::bit_size<IntT>(): {
|
case Numeric::bit_size<IntT>(): {
|
||||||
const IntT temp_carry = destination & 1;
|
const IntT temp_carry = destination & 1;
|
||||||
destination = IntT((destination >> 1) | (carry << (Numeric::bit_size<IntT>() - 1)));
|
destination = IntT((destination >> 1) | (carry << (Numeric::bit_size<IntT>() - 1)));
|
||||||
@@ -69,10 +71,12 @@ void rcl(
|
|||||||
} break;
|
} break;
|
||||||
}
|
}
|
||||||
|
|
||||||
context.flags.template set_from<Flag::Carry>(carry);
|
// Intention: overflow is set if the last shift step affected the top bit of
|
||||||
|
// the destination.
|
||||||
context.flags.template set_from<Flag::Overflow>(
|
context.flags.template set_from<Flag::Overflow>(
|
||||||
((destination >> (Numeric::bit_size<IntT>() - 1)) & 1) ^ carry
|
((destination >> (Numeric::bit_size<IntT>() - 1)) & 1) ^ carry
|
||||||
);
|
);
|
||||||
|
context.flags.template set_from<Flag::Carry>(carry);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename IntT, typename ContextT>
|
template <typename IntT, typename ContextT>
|
||||||
@@ -96,13 +100,12 @@ void rcr(
|
|||||||
OD;
|
OD;
|
||||||
*/
|
*/
|
||||||
auto carry = context.flags.template carry_bit<IntT>();
|
auto carry = context.flags.template carry_bit<IntT>();
|
||||||
context.flags.template set_from<Flag::Overflow>(
|
|
||||||
((destination >> (Numeric::bit_size<IntT>() - 1)) & 1) ^ carry
|
|
||||||
);
|
|
||||||
|
|
||||||
const auto temp_count = count % (Numeric::bit_size<IntT>() + 1);
|
const auto temp_count = count % (Numeric::bit_size<IntT>() + 1);
|
||||||
switch(temp_count) {
|
switch(temp_count) {
|
||||||
case 0: break;
|
case 0:
|
||||||
|
if(!count) return;
|
||||||
|
break;
|
||||||
case Numeric::bit_size<IntT>(): {
|
case Numeric::bit_size<IntT>(): {
|
||||||
const IntT temp_carry = destination & Numeric::top_bit<IntT>();
|
const IntT temp_carry = destination & Numeric::top_bit<IntT>();
|
||||||
destination = IntT((destination << 1) | carry);
|
destination = IntT((destination << 1) | carry);
|
||||||
@@ -119,6 +122,11 @@ void rcr(
|
|||||||
} break;
|
} break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Intention: overflow is set if the last shift step affected the top bit of
|
||||||
|
// the destination.
|
||||||
|
context.flags.template set_from<Flag::Overflow>(
|
||||||
|
(destination ^ (destination << 1)) & Numeric::top_bit<IntT>()
|
||||||
|
);
|
||||||
context.flags.template set_from<Flag::Carry>(carry);
|
context.flags.template set_from<Flag::Carry>(carry);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -336,7 +344,7 @@ void shr(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
context.flags.template set_from<Flag::Overflow>(Numeric::top_bit<IntT>() & destination);
|
context.flags.template set_from<Flag::Overflow>(count == 1 && Numeric::top_bit<IntT>() & destination);
|
||||||
if(count == Numeric::bit_size<IntT>()) {
|
if(count == Numeric::bit_size<IntT>()) {
|
||||||
context.flags.template set_from<Flag::Carry>(Numeric::top_bit<IntT>() & destination);
|
context.flags.template set_from<Flag::Carry>(Numeric::top_bit<IntT>() & destination);
|
||||||
destination = 0;
|
destination = 0;
|
||||||
|
@@ -94,25 +94,38 @@ template <typename IntT, typename ContextT>
|
|||||||
void popa(
|
void popa(
|
||||||
ContextT &context
|
ContextT &context
|
||||||
) {
|
) {
|
||||||
context.memory.preauthorise_stack_read(sizeof(IntT) * 8);
|
const auto initial_sp = context.registers.sp();
|
||||||
if constexpr (std::is_same_v<IntT, uint32_t>) {
|
const auto do_pops = [&] {
|
||||||
context.registers.edi() = pop<uint32_t, true>(context);
|
if constexpr (std::is_same_v<IntT, uint32_t>) {
|
||||||
context.registers.esi() = pop<uint32_t, true>(context);
|
context.registers.edi() = pop<uint32_t, false>(context);
|
||||||
context.registers.ebp() = pop<uint32_t, true>(context);
|
context.registers.esi() = pop<uint32_t, false>(context);
|
||||||
context.registers.esp() += 4;
|
context.registers.ebp() = pop<uint32_t, false>(context);
|
||||||
context.registers.ebx() = pop<uint32_t, true>(context);
|
context.registers.esp() += 4;
|
||||||
context.registers.edx() = pop<uint32_t, true>(context);
|
context.registers.ebx() = pop<uint32_t, false>(context);
|
||||||
context.registers.ecx() = pop<uint32_t, true>(context);
|
context.registers.edx() = pop<uint32_t, false>(context);
|
||||||
context.registers.eax() = pop<uint32_t, true>(context);
|
context.registers.ecx() = pop<uint32_t, false>(context);
|
||||||
|
context.registers.eax() = pop<uint32_t, false>(context);
|
||||||
|
} else {
|
||||||
|
context.registers.di() = pop<uint16_t, false>(context);
|
||||||
|
context.registers.si() = pop<uint16_t, false>(context);
|
||||||
|
context.registers.bp() = pop<uint16_t, false>(context);
|
||||||
|
context.registers.sp() += 2;
|
||||||
|
context.registers.bx() = pop<uint16_t, false>(context);
|
||||||
|
context.registers.dx() = pop<uint16_t, false>(context);
|
||||||
|
context.registers.cx() = pop<uint16_t, false>(context);
|
||||||
|
context.registers.ax() = pop<uint16_t, false>(context);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if(!uses_8086_exceptions(ContextT::model)) {
|
||||||
|
try {
|
||||||
|
do_pops();
|
||||||
|
} catch (const Exception &e) {
|
||||||
|
context.registers.sp() = initial_sp;
|
||||||
|
throw e;
|
||||||
|
};
|
||||||
} else {
|
} else {
|
||||||
context.registers.di() = pop<uint16_t, true>(context);
|
do_pops();
|
||||||
context.registers.si() = pop<uint16_t, true>(context);
|
|
||||||
context.registers.bp() = pop<uint16_t, true>(context);
|
|
||||||
context.registers.sp() += 2;
|
|
||||||
context.registers.bx() = pop<uint16_t, true>(context);
|
|
||||||
context.registers.dx() = pop<uint16_t, true>(context);
|
|
||||||
context.registers.cx() = pop<uint16_t, true>(context);
|
|
||||||
context.registers.ax() = pop<uint16_t, true>(context);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -120,26 +133,38 @@ template <typename IntT, typename ContextT>
|
|||||||
void pusha(
|
void pusha(
|
||||||
ContextT &context
|
ContextT &context
|
||||||
) {
|
) {
|
||||||
context.memory.preauthorise_stack_read(sizeof(IntT) * 8);
|
|
||||||
const IntT initial_sp = context.registers.sp();
|
const IntT initial_sp = context.registers.sp();
|
||||||
if constexpr (std::is_same_v<IntT, uint32_t>) {
|
const auto do_pushes = [&] {
|
||||||
push<uint32_t, true>(context.registers.eax(), context);
|
if constexpr (std::is_same_v<IntT, uint32_t>) {
|
||||||
push<uint32_t, true>(context.registers.ecx(), context);
|
push<uint32_t, false>(context.registers.eax(), context);
|
||||||
push<uint32_t, true>(context.registers.edx(), context);
|
push<uint32_t, false>(context.registers.ecx(), context);
|
||||||
push<uint32_t, true>(context.registers.ebx(), context);
|
push<uint32_t, false>(context.registers.edx(), context);
|
||||||
push<uint32_t, true>(initial_sp, context);
|
push<uint32_t, false>(context.registers.ebx(), context);
|
||||||
push<uint32_t, true>(context.registers.ebp(), context);
|
push<uint32_t, false>(initial_sp, context);
|
||||||
push<uint32_t, true>(context.registers.esi(), context);
|
push<uint32_t, false>(context.registers.ebp(), context);
|
||||||
push<uint32_t, true>(context.registers.edi(), context);
|
push<uint32_t, false>(context.registers.esi(), context);
|
||||||
|
push<uint32_t, false>(context.registers.edi(), context);
|
||||||
|
} else {
|
||||||
|
push<uint16_t, false>(context.registers.ax(), context);
|
||||||
|
push<uint16_t, false>(context.registers.cx(), context);
|
||||||
|
push<uint16_t, false>(context.registers.dx(), context);
|
||||||
|
push<uint16_t, false>(context.registers.bx(), context);
|
||||||
|
push<uint16_t, false>(initial_sp, context);
|
||||||
|
push<uint16_t, false>(context.registers.bp(), context);
|
||||||
|
push<uint16_t, false>(context.registers.si(), context);
|
||||||
|
push<uint16_t, false>(context.registers.di(), context);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if(!uses_8086_exceptions(ContextT::model)) {
|
||||||
|
try {
|
||||||
|
do_pushes();
|
||||||
|
} catch (const Exception &e) {
|
||||||
|
context.registers.sp() = initial_sp;
|
||||||
|
throw e;
|
||||||
|
};
|
||||||
} else {
|
} else {
|
||||||
push<uint16_t, true>(context.registers.ax(), context);
|
do_pushes();
|
||||||
push<uint16_t, true>(context.registers.cx(), context);
|
|
||||||
push<uint16_t, true>(context.registers.dx(), context);
|
|
||||||
push<uint16_t, true>(context.registers.bx(), context);
|
|
||||||
push<uint16_t, true>(initial_sp, context);
|
|
||||||
push<uint16_t, true>(context.registers.bp(), context);
|
|
||||||
push<uint16_t, true>(context.registers.si(), context);
|
|
||||||
push<uint16_t, true>(context.registers.di(), context);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -152,7 +177,11 @@ void enter(
|
|||||||
const auto alloc_size = instruction.dynamic_storage_size();
|
const auto alloc_size = instruction.dynamic_storage_size();
|
||||||
const auto nesting_level = instruction.nesting_level() & 0x1f;
|
const auto nesting_level = instruction.nesting_level() & 0x1f;
|
||||||
|
|
||||||
// Preauthorise contents that'll be fetched via BP.
|
auto final_sp = context.registers.sp();
|
||||||
|
final_sp -= (nesting_level * sizeof(uint16_t)) + alloc_size + sizeof(uint16_t);
|
||||||
|
context.memory.preauthorise_write(Source::SS, final_sp, sizeof(IntT));
|
||||||
|
|
||||||
|
// Preauthorise contents that'll be copied via BP.
|
||||||
const auto copied_pointers = nesting_level - 2;
|
const auto copied_pointers = nesting_level - 2;
|
||||||
if(copied_pointers > 0) {
|
if(copied_pointers > 0) {
|
||||||
context.memory.preauthorise_read(
|
context.memory.preauthorise_read(
|
||||||
@@ -160,10 +189,10 @@ void enter(
|
|||||||
uint16_t(context.registers.bp() - size_t(copied_pointers) * sizeof(uint16_t)),
|
uint16_t(context.registers.bp() - size_t(copied_pointers) * sizeof(uint16_t)),
|
||||||
uint32_t(size_t(copied_pointers) * sizeof(uint16_t)) // TODO: I don't think this can actually be 32 bit.
|
uint32_t(size_t(copied_pointers) * sizeof(uint16_t)) // TODO: I don't think this can actually be 32 bit.
|
||||||
);
|
);
|
||||||
}
|
|
||||||
|
|
||||||
// Preauthorise writes.
|
// Preauthorise stack writes.
|
||||||
context.memory.preauthorise_stack_write(uint32_t(size_t(nesting_level) * sizeof(uint16_t)));
|
// context.memory.preauthorise_stack_write(uint32_t(size_t(copied_pointers) * sizeof(uint16_t)));
|
||||||
|
}
|
||||||
|
|
||||||
// Push BP and grab the end of frame.
|
// Push BP and grab the end of frame.
|
||||||
push<uint16_t, true>(context.registers.bp(), context);
|
push<uint16_t, true>(context.registers.bp(), context);
|
||||||
@@ -175,15 +204,18 @@ void enter(
|
|||||||
context.registers.bp() -= 2;
|
context.registers.bp() -= 2;
|
||||||
|
|
||||||
const auto value =
|
const auto value =
|
||||||
context.memory.template access<uint16_t, AccessType::PreauthorisedRead>(Source::SS, context.registers.bp());
|
context.memory.template access
|
||||||
push<uint16_t, true>(value, context);
|
<uint16_t, AccessType::PreauthorisedRead>(Source::SS, context.registers.bp());
|
||||||
|
push<uint16_t, false>(value, context);
|
||||||
}
|
}
|
||||||
push<uint16_t, true>(frame, context);
|
push<uint16_t, false>(frame, context);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set final BP.
|
// Set final BP.
|
||||||
context.registers.bp() = frame;
|
context.registers.bp() = frame;
|
||||||
context.registers.sp() -= alloc_size;
|
context.registers.sp() -= alloc_size;
|
||||||
|
|
||||||
|
assert(final_sp == context.registers.sp());
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename IntT, typename ContextT>
|
template <typename IntT, typename ContextT>
|
||||||
@@ -192,11 +224,13 @@ void leave(
|
|||||||
) {
|
) {
|
||||||
// TODO: should use StackAddressSize to determine assignment of bp to sp.
|
// TODO: should use StackAddressSize to determine assignment of bp to sp.
|
||||||
if constexpr (std::is_same_v<IntT, uint32_t>) {
|
if constexpr (std::is_same_v<IntT, uint32_t>) {
|
||||||
|
context.memory.preauthorise_read(Source::SS, context.registers.ebp(), sizeof(uint32_t));
|
||||||
context.registers.esp() = context.registers.ebp();
|
context.registers.esp() = context.registers.ebp();
|
||||||
context.registers.ebp() = pop<uint32_t, false>(context);
|
context.registers.ebp() = pop<uint32_t, true>(context);
|
||||||
} else {
|
} else {
|
||||||
|
context.memory.preauthorise_read(Source::SS, context.registers.bp(), sizeof(uint16_t));
|
||||||
context.registers.sp() = context.registers.bp();
|
context.registers.sp() = context.registers.bp();
|
||||||
context.registers.bp() = pop<uint16_t, false>(context);
|
context.registers.bp() = pop<uint16_t, true>(context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -8,6 +8,7 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "Exceptions.hpp"
|
||||||
#include "Model.hpp"
|
#include "Model.hpp"
|
||||||
|
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
@@ -414,11 +415,11 @@ template <DataSize size> struct DataSizeType { using type = uint8_t; };
|
|||||||
template <> struct DataSizeType<DataSize::Word> { using type = uint16_t; };
|
template <> struct DataSizeType<DataSize::Word> { using type = uint16_t; };
|
||||||
template <> struct DataSizeType<DataSize::DWord> { using type = uint32_t; };
|
template <> struct DataSizeType<DataSize::DWord> { using type = uint32_t; };
|
||||||
|
|
||||||
constexpr int byte_size(DataSize size) {
|
constexpr int byte_size(const DataSize size) {
|
||||||
return (1 << int(size)) & 7;
|
return (1 << int(size)) & 7;
|
||||||
}
|
}
|
||||||
|
|
||||||
constexpr int bit_size(DataSize size) {
|
constexpr int bit_size(const DataSize size) {
|
||||||
return (8 << int(size)) & 0x3f;
|
return (8 << int(size)) & 0x3f;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -430,15 +431,15 @@ enum class AddressSize: uint8_t {
|
|||||||
template <AddressSize size> struct AddressSizeType { using type = uint16_t; };
|
template <AddressSize size> struct AddressSizeType { using type = uint16_t; };
|
||||||
template <> struct AddressSizeType<AddressSize::b32> { using type = uint32_t; };
|
template <> struct AddressSizeType<AddressSize::b32> { using type = uint32_t; };
|
||||||
|
|
||||||
constexpr DataSize data_size(AddressSize size) {
|
constexpr DataSize data_size(const AddressSize size) {
|
||||||
return DataSize(int(size) + 1);
|
return DataSize(int(size) + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
constexpr int byte_size(AddressSize size) {
|
constexpr int byte_size(const AddressSize size) {
|
||||||
return 2 << int(size);
|
return 2 << int(size);
|
||||||
}
|
}
|
||||||
|
|
||||||
constexpr int bit_size(AddressSize size) {
|
constexpr int bit_size(const AddressSize size) {
|
||||||
return 16 << int(size);
|
return 16 << int(size);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -505,6 +506,10 @@ constexpr bool is_register(const Source source) {
|
|||||||
constexpr bool is_segment_register(const Source source) {
|
constexpr bool is_segment_register(const Source source) {
|
||||||
return is_register(source) && source >= Source::ES;
|
return is_register(source) && source >= Source::ES;
|
||||||
}
|
}
|
||||||
|
constexpr bool is_address(const Source source) {
|
||||||
|
// TODO: is there any reason not to swap DirectAddress and Immediate to simplify this?
|
||||||
|
return source == Source::Indirect || source == Source::IndirectNoBase || source == Source::DirectAddress;
|
||||||
|
}
|
||||||
|
|
||||||
enum class Repetition: uint8_t {
|
enum class Repetition: uint8_t {
|
||||||
None, RepE, RepNE, Rep,
|
None, RepE, RepNE, Rep,
|
||||||
@@ -512,7 +517,7 @@ enum class Repetition: uint8_t {
|
|||||||
|
|
||||||
/// @returns @c true if @c operation supports repetition mode @c repetition; @c false otherwise.
|
/// @returns @c true if @c operation supports repetition mode @c repetition; @c false otherwise.
|
||||||
template <Model model>
|
template <Model model>
|
||||||
constexpr Operation rep_operation(Operation operation, Repetition repetition) {
|
constexpr Operation rep_operation(const Operation operation, const Repetition repetition) {
|
||||||
switch(operation) {
|
switch(operation) {
|
||||||
case Operation::IDIV:
|
case Operation::IDIV:
|
||||||
if constexpr (model == Model::i8086) {
|
if constexpr (model == Model::i8086) {
|
||||||
@@ -565,15 +570,15 @@ constexpr Operation rep_operation(Operation operation, Repetition repetition) {
|
|||||||
class ScaleIndexBase {
|
class ScaleIndexBase {
|
||||||
public:
|
public:
|
||||||
constexpr ScaleIndexBase() noexcept = default;
|
constexpr ScaleIndexBase() noexcept = default;
|
||||||
constexpr ScaleIndexBase(uint8_t sib) noexcept : sib_(sib) {}
|
constexpr ScaleIndexBase(const uint8_t sib) noexcept : sib_(sib) {}
|
||||||
constexpr ScaleIndexBase(int scale, Source index, Source base) noexcept :
|
constexpr ScaleIndexBase(const int scale, const Source index, const Source base) noexcept :
|
||||||
sib_(uint8_t(
|
sib_(uint8_t(
|
||||||
scale << 6 |
|
scale << 6 |
|
||||||
(int(index != Source::None ? index : Source::eSP) << 3) |
|
(int(index != Source::None ? index : Source::eSP) << 3) |
|
||||||
int(base)
|
int(base)
|
||||||
)) {}
|
)) {}
|
||||||
constexpr ScaleIndexBase(Source index, Source base) noexcept : ScaleIndexBase(0, index, base) {}
|
constexpr ScaleIndexBase(const Source index, const Source base) noexcept : ScaleIndexBase(0, index, base) {}
|
||||||
constexpr explicit ScaleIndexBase(Source base) noexcept : ScaleIndexBase(0, Source::None, base) {}
|
constexpr explicit ScaleIndexBase(const Source base) noexcept : ScaleIndexBase(0, Source::None, base) {}
|
||||||
|
|
||||||
/// @returns the power of two by which to multiply @c index() before adding it to @c base().
|
/// @returns the power of two by which to multiply @c index() before adding it to @c base().
|
||||||
constexpr int scale() const {
|
constexpr int scale() const {
|
||||||
@@ -634,18 +639,18 @@ static_assert(alignof(ScaleIndexBase) == 1);
|
|||||||
class DataPointer {
|
class DataPointer {
|
||||||
public:
|
public:
|
||||||
/// Constricts a DataPointer referring to the given source; it shouldn't be ::Indirect.
|
/// Constricts a DataPointer referring to the given source; it shouldn't be ::Indirect.
|
||||||
constexpr DataPointer(Source source) noexcept : source_(source) {}
|
constexpr DataPointer(const Source source) noexcept : source_(source) {}
|
||||||
|
|
||||||
/// Constricts a DataPointer with a source of ::Indirect and the specified sib.
|
/// Constricts a DataPointer with a source of ::Indirect and the specified sib.
|
||||||
constexpr DataPointer(ScaleIndexBase sib) noexcept : sib_(sib) {}
|
constexpr DataPointer(const ScaleIndexBase sib) noexcept : sib_(sib) {}
|
||||||
|
|
||||||
/// Constructs a DataPointer with a source and SIB; use the source to indicate
|
/// Constructs a DataPointer with a source and SIB; use the source to indicate
|
||||||
/// whether the base field of the SIB is effective.
|
/// whether the base field of the SIB is effective.
|
||||||
constexpr DataPointer(Source source, ScaleIndexBase sib) noexcept : source_(source), sib_(sib) {}
|
constexpr DataPointer(const Source source, const ScaleIndexBase sib) noexcept : source_(source), sib_(sib) {}
|
||||||
|
|
||||||
/// Constructs an indirect DataPointer referencing the given base, index and scale.
|
/// Constructs an indirect DataPointer referencing the given base, index and scale.
|
||||||
/// Automatically maps Source::Indirect to Source::IndirectNoBase if base is Source::None.
|
/// Automatically maps Source::Indirect to Source::IndirectNoBase if base is Source::None.
|
||||||
constexpr DataPointer(Source base, Source index, int scale) noexcept :
|
constexpr DataPointer(const Source base, const Source index, const int scale) noexcept :
|
||||||
source_(base != Source::None ? Source::Indirect : Source::IndirectNoBase),
|
source_(base != Source::None ? Source::Indirect : Source::IndirectNoBase),
|
||||||
sib_(scale, index, base) {}
|
sib_(scale, index, base) {}
|
||||||
|
|
||||||
@@ -704,7 +709,7 @@ public:
|
|||||||
using AddressT = ImmediateT;
|
using AddressT = ImmediateT;
|
||||||
|
|
||||||
constexpr Instruction() noexcept = default;
|
constexpr Instruction() noexcept = default;
|
||||||
constexpr Instruction(Operation operation) noexcept :
|
constexpr Instruction(const Operation operation) noexcept :
|
||||||
Instruction(
|
Instruction(
|
||||||
operation,
|
operation,
|
||||||
Source::None,
|
Source::None,
|
||||||
@@ -718,16 +723,16 @@ public:
|
|||||||
0
|
0
|
||||||
) {}
|
) {}
|
||||||
constexpr Instruction(
|
constexpr Instruction(
|
||||||
Operation operation,
|
const Operation operation,
|
||||||
Source source,
|
const Source source,
|
||||||
Source destination,
|
const Source destination,
|
||||||
ScaleIndexBase sib,
|
const ScaleIndexBase sib,
|
||||||
bool lock,
|
const bool lock,
|
||||||
AddressSize address_size,
|
const AddressSize address_size,
|
||||||
Source segment_override,
|
const Source segment_override,
|
||||||
DataSize data_size,
|
const DataSize data_size,
|
||||||
DisplacementT displacement,
|
const DisplacementT displacement,
|
||||||
ImmediateT operand) noexcept :
|
const ImmediateT operand) noexcept :
|
||||||
operation_(operation),
|
operation_(operation),
|
||||||
mem_exts_source_(uint8_t(
|
mem_exts_source_(uint8_t(
|
||||||
(int(address_size) << 7) |
|
(int(address_size) << 7) |
|
||||||
@@ -763,6 +768,13 @@ public:
|
|||||||
source_data_dest_sib_ |= (int(segment)&7) << 10;
|
source_data_dest_sib_ |= (int(segment)&7) << 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Instantiates an Instruction as a particular GPF. This was originally intended to support decoding of
|
||||||
|
/// overlong instructions.
|
||||||
|
constexpr Instruction(const ExceptionCode gpf_code) noexcept :
|
||||||
|
operation_(Operation::Invalid),
|
||||||
|
mem_exts_source_(1),
|
||||||
|
source_data_dest_sib_(uint16_t(gpf_code)) {}
|
||||||
|
|
||||||
/// @returns The number of bytes used for meaningful content within this class. A receiver must use at least @c sizeof(Instruction) bytes
|
/// @returns The number of bytes used for meaningful content within this class. A receiver must use at least @c sizeof(Instruction) bytes
|
||||||
/// to store an @c Instruction but is permitted to reuse the trailing sizeof(Instruction) - packing_size() for any purpose it likes. Teleologically,
|
/// to store an @c Instruction but is permitted to reuse the trailing sizeof(Instruction) - packing_size() for any purpose it likes. Teleologically,
|
||||||
/// this allows a denser packing of instructions into containers.
|
/// this allows a denser packing of instructions into containers.
|
||||||
@@ -851,10 +863,21 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// @returns The dynamic storage size argument supplied to an ENTER.
|
/// @returns The dynamic storage size argument supplied to an ENTER.
|
||||||
constexpr ImmediateT dynamic_storage_size() const {
|
constexpr ImmediateT dynamic_storage_size() const {
|
||||||
return offset();
|
return offset();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// If this Instruction's operation is ::Invalid, indicates whether the proper exception
|
||||||
|
/// to throw is a GPF rather than an InvalidOpcode.
|
||||||
|
constexpr bool invalid_is_gpf() const {
|
||||||
|
return mem_exts_source_ & 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If this Instruction represents a GPF, indicates the corresponding exception code.
|
||||||
|
constexpr ExceptionCode gpf_exception_code() const {
|
||||||
|
return ExceptionCode(source_data_dest_sib_);
|
||||||
|
}
|
||||||
|
|
||||||
// Standard comparison operator.
|
// Standard comparison operator.
|
||||||
constexpr bool operator ==(const Instruction<type> &rhs) const {
|
constexpr bool operator ==(const Instruction<type> &rhs) const {
|
||||||
if( operation_ != rhs.operation_ ||
|
if( operation_ != rhs.operation_ ||
|
||||||
@@ -932,12 +955,12 @@ static_assert(sizeof(Instruction<InstructionType::Bits16>) <= 10);
|
|||||||
//
|
//
|
||||||
|
|
||||||
/// @returns @c true if @c operation uses a @c displacement().
|
/// @returns @c true if @c operation uses a @c displacement().
|
||||||
bool has_displacement(Operation operation);
|
bool has_displacement(const Operation operation);
|
||||||
|
|
||||||
/// @returns The maximum number of operands to print in a disassembly of @c operation;
|
/// @returns The maximum number of operands to print in a disassembly of @c operation;
|
||||||
/// i.e. 2 for both source() and destination(), 1 for source() alone, 0 for neither. This is a maximum
|
/// i.e. 2 for both source() and destination(), 1 for source() alone, 0 for neither. This is a maximum
|
||||||
/// only — if either source is Source::None then it should not be printed.
|
/// only — if either source is Source::None then it should not be printed.
|
||||||
int max_displayed_operands(Operation operation);
|
int max_displayed_operands(const Operation operation);
|
||||||
|
|
||||||
/// Provides the idiomatic name of the @c Operation given an operation @c DataSize and processor @c Model.
|
/// Provides the idiomatic name of the @c Operation given an operation @c DataSize and processor @c Model.
|
||||||
std::string to_string(Operation, DataSize, Model);
|
std::string to_string(Operation, DataSize, Model);
|
||||||
@@ -957,11 +980,11 @@ std::string to_string(Source, DataSize);
|
|||||||
/// If @c operation_size is the default value of @c ::None, it'll be taken from the @c instruction.
|
/// If @c operation_size is the default value of @c ::None, it'll be taken from the @c instruction.
|
||||||
template <InstructionType type>
|
template <InstructionType type>
|
||||||
std::string to_string(
|
std::string to_string(
|
||||||
DataPointer pointer,
|
const DataPointer pointer,
|
||||||
Instruction<type> instruction,
|
const Instruction<type> instruction,
|
||||||
int offset_length,
|
const int offset_length,
|
||||||
int immediate_length,
|
const int immediate_length,
|
||||||
DataSize operation_size = InstructionSet::x86::DataSize::None
|
const DataSize operation_size = InstructionSet::x86::DataSize::None
|
||||||
);
|
);
|
||||||
|
|
||||||
/// Provides the printable version of @c instruction.
|
/// Provides the printable version of @c instruction.
|
||||||
@@ -972,8 +995,8 @@ std::string to_string(
|
|||||||
/// If @c immediate_length is '2' or '4', truncates any printed immediate value to 2 or 4 digits if it is compatible with being that length.
|
/// If @c immediate_length is '2' or '4', truncates any printed immediate value to 2 or 4 digits if it is compatible with being that length.
|
||||||
template <InstructionType type>
|
template <InstructionType type>
|
||||||
std::string to_string(
|
std::string to_string(
|
||||||
std::pair<int, Instruction<type>> instruction,
|
const std::pair<int, Instruction<type>> instruction,
|
||||||
Model model,
|
const Model model,
|
||||||
int offset_length = 0,
|
const int offset_length = 0,
|
||||||
int immediate_length = 0);
|
const int immediate_length = 0);
|
||||||
}
|
}
|
||||||
|
@@ -204,6 +204,7 @@ concept is_flow_controller_16 = requires(FlowControllerT controller) {
|
|||||||
controller.halt();
|
controller.halt();
|
||||||
controller.wait();
|
controller.wait();
|
||||||
controller.repeat_last();
|
controller.repeat_last();
|
||||||
|
controller.cancel_repetition();
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename FlowControllerT, Model model>
|
template <typename FlowControllerT, Model model>
|
||||||
|
@@ -249,7 +249,6 @@ struct LinearMemory<InstructionSet::x86::Model::i80286>: public LinearPool<1 <<
|
|||||||
typename InstructionSet::x86::Accessor<IntT, type>::type access(
|
typename InstructionSet::x86::Accessor<IntT, type>::type access(
|
||||||
uint32_t address, uint32_t
|
uint32_t address, uint32_t
|
||||||
) {
|
) {
|
||||||
// 80286: never split (probably?).
|
|
||||||
return *reinterpret_cast<IntT *>(&memory[address & address_mask_]);
|
return *reinterpret_cast<IntT *>(&memory[address & address_mask_]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -258,8 +257,6 @@ struct LinearMemory<InstructionSet::x86::Model::i80286>: public LinearPool<1 <<
|
|||||||
uint32_t address, uint32_t
|
uint32_t address, uint32_t
|
||||||
) const {
|
) const {
|
||||||
static_assert(!is_writeable(type));
|
static_assert(!is_writeable(type));
|
||||||
|
|
||||||
// 80286: never split (probably?).
|
|
||||||
return *reinterpret_cast<const IntT *>(&memory[address & address_mask_]);
|
return *reinterpret_cast<const IntT *>(&memory[address & address_mask_]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -538,6 +538,9 @@ public:
|
|||||||
void repeat_last() {
|
void repeat_last() {
|
||||||
should_repeat_ = true;
|
should_repeat_ = true;
|
||||||
}
|
}
|
||||||
|
void cancel_repetition() {
|
||||||
|
should_repeat_ = false;
|
||||||
|
}
|
||||||
|
|
||||||
// Other actions.
|
// Other actions.
|
||||||
void begin_instruction() {
|
void begin_instruction() {
|
||||||
@@ -938,6 +941,7 @@ private:
|
|||||||
KeyboardController<pc_model> &keyboard,
|
KeyboardController<pc_model> &keyboard,
|
||||||
RTC &rtc
|
RTC &rtc
|
||||||
) :
|
) :
|
||||||
|
flags(x86_model),
|
||||||
segments(registers, linear_memory),
|
segments(registers, linear_memory),
|
||||||
memory(registers, segments, linear_memory),
|
memory(registers, segments, linear_memory),
|
||||||
flow_controller(registers, segments),
|
flow_controller(registers, segments),
|
||||||
|
@@ -152,8 +152,12 @@ public:
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void preauthorise_read(InstructionSet::x86::Source, uint16_t, uint32_t) {}
|
void preauthorise_read(const InstructionSet::x86::Source descriptor, const uint16_t offset, const uint32_t size) {
|
||||||
void preauthorise_write(InstructionSet::x86::Source, uint16_t, uint32_t) {}
|
segments_.descriptors[descriptor].template authorise<InstructionSet::x86::AccessType::Read, uint16_t>(offset, offset + size);
|
||||||
|
}
|
||||||
|
void preauthorise_write(const InstructionSet::x86::Source descriptor, const uint16_t offset, const uint32_t size) {
|
||||||
|
segments_.descriptors[descriptor].template authorise<InstructionSet::x86::AccessType::Write, uint16_t>(offset, offset + size);
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: perform authorisation checks.
|
// TODO: perform authorisation checks.
|
||||||
|
|
||||||
|
@@ -31,11 +31,34 @@
|
|||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
|
NSSet *const allowList = [NSSet setWithArray:@[
|
||||||
|
// Current execution failures, albeit all permitted:
|
||||||
|
// @"D4.json.gz", // AAM
|
||||||
|
// @"F6.7.json.gz", // IDIV byte
|
||||||
|
// @"F7.7.json.gz", // IDIV word
|
||||||
|
// @"00.json.gz",
|
||||||
|
|
||||||
|
// @"61.json.gz", // POPA
|
||||||
|
// @"C6.json.gz",
|
||||||
|
// @"C7.json.gz",
|
||||||
|
// @"C8.json.gz", // ENTER
|
||||||
|
// @"CD.json.gz",
|
||||||
|
// @"CE.json.gz", // INTO
|
||||||
|
// @"D8.json.gz", // Various floating point
|
||||||
|
// @"F6.7.json.gz", // IDIV
|
||||||
|
// @"FF.3.json.gz", // CALL far
|
||||||
|
// @"FF.5.json.gz"
|
||||||
|
]];
|
||||||
|
|
||||||
|
// MARK: - Test paths
|
||||||
|
|
||||||
// The tests themselves are not duplicated in this repository;
|
// The tests themselves are not duplicated in this repository;
|
||||||
// provide their real path here.
|
// provide their real paths here.
|
||||||
constexpr char TestSuiteHome8088[] = "/Users/thomasharte/Projects/8088/v1";
|
constexpr char TestSuiteHome8088[] = "/Users/thomasharte/Projects/8088/v1";
|
||||||
constexpr char TestSuiteHome80286[] = "/Users/thomasharte/Projects/80286/v1_real_mode";
|
constexpr char TestSuiteHome80286[] = "/Users/thomasharte/Projects/80286/v1_real_mode";
|
||||||
|
|
||||||
|
// MARK: - Virtual machine
|
||||||
|
|
||||||
using Flags = InstructionSet::x86::Flags;
|
using Flags = InstructionSet::x86::Flags;
|
||||||
|
|
||||||
template <InstructionSet::x86::Model t_model>
|
template <InstructionSet::x86::Model t_model>
|
||||||
@@ -148,7 +171,10 @@ struct IO {
|
|||||||
template <InstructionSet::x86::Model t_model>
|
template <InstructionSet::x86::Model t_model>
|
||||||
class FlowController {
|
class FlowController {
|
||||||
public:
|
public:
|
||||||
FlowController(InstructionSet::x86::Registers<t_model> ®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) {}
|
registers_(registers), segments_(segments) {}
|
||||||
|
|
||||||
// Requirements for perform.
|
// Requirements for perform.
|
||||||
@@ -168,25 +194,38 @@ public:
|
|||||||
registers_.ip() = address;
|
registers_.ip() = address;
|
||||||
}
|
}
|
||||||
|
|
||||||
void halt() {}
|
void halt() {
|
||||||
|
is_halted_ = true;
|
||||||
|
}
|
||||||
void wait() {}
|
void wait() {}
|
||||||
|
|
||||||
void repeat_last() {
|
void repeat_last() {
|
||||||
should_repeat_ = true;
|
should_repeat_ = true;
|
||||||
}
|
}
|
||||||
|
void cancel_repetition() {
|
||||||
|
should_repeat_ = false;
|
||||||
|
}
|
||||||
|
|
||||||
// Other actions.
|
// Other actions.
|
||||||
void begin_instruction() {
|
void begin_instruction() {
|
||||||
should_repeat_ = false;
|
is_halted_ = should_repeat_ = false;
|
||||||
}
|
}
|
||||||
bool should_repeat() const {
|
bool should_repeat() const {
|
||||||
return should_repeat_;
|
return should_repeat_;
|
||||||
}
|
}
|
||||||
|
bool is_halted() const {
|
||||||
|
return is_halted_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void clear() {
|
||||||
|
should_repeat_ = is_halted_ = false;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
InstructionSet::x86::Registers<t_model> ®isters_;
|
InstructionSet::x86::Registers<t_model> ®isters_;
|
||||||
PCCompatible::Segments<t_model, LinearMemory<t_model>> &segments_;
|
PCCompatible::Segments<t_model, LinearMemory<t_model>> &segments_;
|
||||||
bool should_repeat_ = false;
|
bool should_repeat_ = false;
|
||||||
|
bool is_halted_ = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct CPUControl {
|
struct CPUControl {
|
||||||
@@ -215,15 +254,19 @@ struct ExecutionSupport {
|
|||||||
CPUControl cpu_control;
|
CPUControl cpu_control;
|
||||||
|
|
||||||
ExecutionSupport():
|
ExecutionSupport():
|
||||||
|
flags(model),
|
||||||
memory(registers, segments, linear_memory),
|
memory(registers, segments, linear_memory),
|
||||||
segments(registers, linear_memory),
|
segments(registers, linear_memory),
|
||||||
flow_controller(registers, segments) {}
|
flow_controller(registers, segments) {}
|
||||||
|
|
||||||
void clear() {
|
void clear() {
|
||||||
linear_memory.clear();
|
linear_memory.clear();
|
||||||
|
flow_controller.clear();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// MARK: - Test helpers
|
||||||
|
|
||||||
struct FailedExecution {
|
struct FailedExecution {
|
||||||
std::string test_name;
|
std::string test_name;
|
||||||
std::string reason;
|
std::string reason;
|
||||||
@@ -242,15 +285,8 @@ std::vector<uint8_t> bytes(NSArray<NSNumber *> *encoding) {
|
|||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
NSArray<NSString *> *testFiles(const char *const home) {
|
NSArray<NSString *> *test_files(const char *const home) {
|
||||||
NSString *const path = [NSString stringWithUTF8String:home];
|
NSString *const path = [NSString stringWithUTF8String:home];
|
||||||
NSSet *const allowList = [NSSet setWithArray:@[
|
|
||||||
// Current execution failures, albeit all permitted:
|
|
||||||
// @"D4.json.gz", // AAM
|
|
||||||
// @"F6.7.json.gz", // IDIV byte
|
|
||||||
// @"F7.7.json.gz", // IDIV word
|
|
||||||
]];
|
|
||||||
|
|
||||||
NSSet *ignoreList = nil;
|
NSSet *ignoreList = nil;
|
||||||
|
|
||||||
NSArray<NSString *> *files = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:path error:nil];
|
NSArray<NSString *> *files = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:path error:nil];
|
||||||
@@ -275,7 +311,7 @@ NSArray<NSString *> *testFiles(const char *const home) {
|
|||||||
return [fullPaths sortedArrayUsingSelector:@selector(compare:)];
|
return [fullPaths sortedArrayUsingSelector:@selector(compare:)];
|
||||||
}
|
}
|
||||||
|
|
||||||
NSArray<NSDictionary *> *testsInFile(NSString *file) {
|
NSArray<NSDictionary *> *tests_in_file(NSString *file) {
|
||||||
NSData *data = [NSData dataWithContentsOfGZippedFile:file];
|
NSData *data = [NSData dataWithContentsOfGZippedFile:file];
|
||||||
return [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
|
return [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
|
||||||
}
|
}
|
||||||
@@ -287,6 +323,8 @@ NSDictionary *metadata(const char *home) {
|
|||||||
|
|
||||||
template <InstructionSet::x86::Model t_model>
|
template <InstructionSet::x86::Model t_model>
|
||||||
void populate(InstructionSet::x86::Registers<t_model> ®isters, Flags &flags, NSDictionary *value) {
|
void populate(InstructionSet::x86::Registers<t_model> ®isters, Flags &flags, NSDictionary *value) {
|
||||||
|
registers.reset();
|
||||||
|
|
||||||
registers.ax() = [value[@"ax"] intValue];
|
registers.ax() = [value[@"ax"] intValue];
|
||||||
registers.bx() = [value[@"bx"] intValue];
|
registers.bx() = [value[@"bx"] intValue];
|
||||||
registers.cx() = [value[@"cx"] intValue];
|
registers.cx() = [value[@"cx"] intValue];
|
||||||
@@ -324,6 +362,8 @@ void populate(InstructionSet::x86::Registers<t_model> ®isters, Flags &flags,
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - Test runners; execution
|
||||||
|
|
||||||
template <InstructionSet::x86::Model t_model>
|
template <InstructionSet::x86::Model t_model>
|
||||||
void apply_execution_test(
|
void apply_execution_test(
|
||||||
ExecutionSupport<t_model> &execution_support,
|
ExecutionSupport<t_model> &execution_support,
|
||||||
@@ -332,10 +372,24 @@ void apply_execution_test(
|
|||||||
NSDictionary *test,
|
NSDictionary *test,
|
||||||
NSDictionary *metadata
|
NSDictionary *metadata
|
||||||
) {
|
) {
|
||||||
|
// NSLog(@"%@", test[@"hash"]);
|
||||||
|
// if(![test[@"hash"] isEqualToString:@"ed7087ccf6036e30365a290ac7598d7a3cb93b43"]) {
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
|
||||||
InstructionSet::x86::Decoder<t_model> decoder;
|
InstructionSet::x86::Decoder<t_model> decoder;
|
||||||
const auto data = bytes(test[@"bytes"]);
|
const auto data = bytes(test[@"bytes"]);
|
||||||
const auto decoded = decoder.decode(data.data(), data.size());
|
const auto decoded = decoder.decode(data.data(), data.size());
|
||||||
|
|
||||||
|
if(decoded.first < 0) {
|
||||||
|
FailedExecution failure;
|
||||||
|
failure.instruction = decoded.second;
|
||||||
|
failure.test_name = std::string([[test[@"name"] stringByAppendingFormat:@"; hash: %@", test[@"hash"]] UTF8String]);
|
||||||
|
failure.reason = "Failed to decode";
|
||||||
|
execution_failures.push_back(std::move(failure));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
execution_support.clear();
|
execution_support.clear();
|
||||||
|
|
||||||
const uint16_t flags_mask = metadata[@"flags-mask"] ? [metadata[@"flags-mask"] intValue] : 0xffff;
|
const uint16_t flags_mask = metadata[@"flags-mask"] ? [metadata[@"flags-mask"] intValue] : 0xffff;
|
||||||
@@ -343,7 +397,7 @@ void apply_execution_test(
|
|||||||
NSDictionary *const final_state = test[@"final"];
|
NSDictionary *const final_state = test[@"final"];
|
||||||
|
|
||||||
// Apply initial state.
|
// Apply initial state.
|
||||||
Flags initial_flags;
|
Flags initial_flags(t_model);
|
||||||
for(NSArray<NSNumber *> *ram in initial_state[@"ram"]) {
|
for(NSArray<NSNumber *> *ram in initial_state[@"ram"]) {
|
||||||
execution_support.linear_memory.seed([ram[0] intValue], [ram[1] intValue]);
|
execution_support.linear_memory.seed([ram[0] intValue], [ram[1] intValue]);
|
||||||
}
|
}
|
||||||
@@ -364,8 +418,6 @@ void apply_execution_test(
|
|||||||
execution_support.registers.ip() += decoded.first;
|
execution_support.registers.ip() += decoded.first;
|
||||||
do {
|
do {
|
||||||
execution_support.flow_controller.begin_instruction();
|
execution_support.flow_controller.begin_instruction();
|
||||||
// TODO: catch and process exceptions, which I think means better factoring
|
|
||||||
// re: PCCompatible/instruction set.
|
|
||||||
InstructionSet::x86::perform(
|
InstructionSet::x86::perform(
|
||||||
decoded.second,
|
decoded.second,
|
||||||
execution_support,
|
execution_support,
|
||||||
@@ -373,16 +425,25 @@ void apply_execution_test(
|
|||||||
);
|
);
|
||||||
} while (execution_support.flow_controller.should_repeat());
|
} while (execution_support.flow_controller.should_repeat());
|
||||||
|
|
||||||
|
// If this is the 80286 test set then there was also a HLT. Act as if that happened.
|
||||||
|
// Unless the processor was already halted.
|
||||||
|
if constexpr (t_model == InstructionSet::x86::Model::i80286) {
|
||||||
|
if(!execution_support.flow_controller.is_halted()) {
|
||||||
|
++execution_support.registers.ip();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Compare final state.
|
// Compare final state.
|
||||||
InstructionSet::x86::Registers<t_model> intended_registers;
|
InstructionSet::x86::Registers<t_model> intended_registers;
|
||||||
InstructionSet::x86::Flags intended_flags;
|
InstructionSet::x86::Flags intended_flags(t_model);
|
||||||
|
|
||||||
bool ramEqual = true;
|
bool ramEqual = true;
|
||||||
int mask_position = 0;
|
int mask_position = 0;
|
||||||
for(NSArray<NSNumber *> *ram in final_state[@"ram"]) {
|
for(NSArray<NSNumber *> *ram in final_state[@"ram"]) {
|
||||||
const uint32_t address = [ram[0] intValue];
|
const uint32_t address = [ram[0] intValue];
|
||||||
const auto value =
|
const auto value =
|
||||||
execution_support.linear_memory.template access<uint8_t, InstructionSet::x86::AccessType::Read>(address, address);
|
execution_support.linear_memory.template access<uint8_t, InstructionSet::x86::AccessType::Read>
|
||||||
|
(address, address);
|
||||||
|
|
||||||
if((mask_position != 1) && value == [ram[1] intValue]) {
|
if((mask_position != 1) && value == [ram[1] intValue]) {
|
||||||
continue;
|
continue;
|
||||||
@@ -480,23 +541,26 @@ void apply_execution_test(
|
|||||||
// Record a failure.
|
// Record a failure.
|
||||||
FailedExecution failure;
|
FailedExecution failure;
|
||||||
failure.instruction = decoded.second;
|
failure.instruction = decoded.second;
|
||||||
failure.test_name = std::string([test[@"name"] UTF8String]);
|
failure.test_name = std::string([[test[@"name"] stringByAppendingFormat:@"; hash: %@", test[@"hash"]] UTF8String]);
|
||||||
|
|
||||||
NSMutableArray<NSString *> *reasons = [[NSMutableArray alloc] init];
|
NSMutableArray<NSString *> *reasons = [[NSMutableArray alloc] init];
|
||||||
if(!flagsEqual) {
|
if(!flagsEqual) {
|
||||||
Flags difference;
|
Flags difference(t_model);
|
||||||
difference.set((intended_flags.get() ^ execution_support.flags.get()) & flags_mask);
|
difference.set((intended_flags.get() ^ execution_support.flags.get()) & flags_mask);
|
||||||
[reasons addObject:
|
[reasons addObject:
|
||||||
[NSString stringWithFormat:@"flags differ; errors in %s",
|
[NSString stringWithFormat:@"flags differ; errors in %s due to final state %s",
|
||||||
difference.to_string().c_str()]];
|
difference.to_string().c_str(), execution_support.flags.to_string().c_str()]];
|
||||||
}
|
}
|
||||||
if(!registersEqual) {
|
if(!registersEqual) {
|
||||||
NSMutableArray<NSString *> *registers = [[NSMutableArray alloc] init];
|
NSMutableArray<NSString *> *registers = [[NSMutableArray alloc] init];
|
||||||
|
bool is_first = true;
|
||||||
#define Reg(x) \
|
#define Reg(x) \
|
||||||
if(intended_registers.x() != execution_support.registers.x()) \
|
if(intended_registers.x() != execution_support.registers.x()) { \
|
||||||
[registers addObject: \
|
[registers addObject: \
|
||||||
[NSString stringWithFormat: \
|
[NSString stringWithFormat: \
|
||||||
@#x" is %04x rather than %04x", execution_support.registers.x(), intended_registers.x()]];
|
@#x" is %04x %@ %04x", execution_support.registers.x(), is_first ? @"but should have been" : @"not", intended_registers.x()]]; \
|
||||||
|
is_first = false; \
|
||||||
|
}
|
||||||
|
|
||||||
Reg(ax);
|
Reg(ax);
|
||||||
Reg(cx);
|
Reg(cx);
|
||||||
@@ -528,12 +592,12 @@ void apply_execution_test(
|
|||||||
template <InstructionSet::x86::Model t_model>
|
template <InstructionSet::x86::Model t_model>
|
||||||
void test_execution(const char *const home) {
|
void test_execution(const char *const home) {
|
||||||
NSDictionary *metadatas = metadata(home);
|
NSDictionary *metadatas = metadata(home);
|
||||||
NSMutableArray<NSString *> *failures = [[NSMutableArray alloc] init];
|
NSMutableDictionary<NSString *, NSNumber *> *failures = [[NSMutableDictionary alloc] init];
|
||||||
std::vector<FailedExecution> execution_failures;
|
std::vector<FailedExecution> execution_failures;
|
||||||
std::vector<FailedExecution> permitted_failures;
|
std::vector<FailedExecution> permitted_failures;
|
||||||
auto execution_support = std::make_unique<ExecutionSupport<t_model>>();
|
auto execution_support = std::make_unique<ExecutionSupport<t_model>>();
|
||||||
|
|
||||||
for(NSString *file in testFiles(home)) @autoreleasepool {
|
for(NSString *file in test_files(home)) @autoreleasepool {
|
||||||
const auto failures_before = execution_failures.size();
|
const auto failures_before = execution_failures.size();
|
||||||
|
|
||||||
// Determine the metadata key.
|
// Determine the metadata key.
|
||||||
@@ -548,14 +612,12 @@ void test_execution(const char *const home) {
|
|||||||
test_metadata[@"reg"][[NSString stringWithFormat:@"%c", [name characterAtIndex:first_dot.location+1]]];
|
test_metadata[@"reg"][[NSString stringWithFormat:@"%c", [name characterAtIndex:first_dot.location+1]]];
|
||||||
}
|
}
|
||||||
|
|
||||||
// int index = 0;
|
for(NSDictionary *test in tests_in_file(file)) {
|
||||||
for(NSDictionary *test in testsInFile(file)) {
|
|
||||||
apply_execution_test(*execution_support, execution_failures, permitted_failures, test, test_metadata);
|
apply_execution_test(*execution_support, execution_failures, permitted_failures, test, test_metadata);
|
||||||
// ++index;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (execution_failures.size() != failures_before) {
|
if (execution_failures.size() != failures_before) {
|
||||||
[failures addObject:file];
|
failures[file] = @([failures[file] intValue] + execution_failures.size() - failures_before);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -569,28 +631,25 @@ void test_execution(const char *const home) {
|
|||||||
NSLog(@"Permitted failure of %s — %s", failure.test_name.c_str(), failure.reason.c_str());
|
NSLog(@"Permitted failure of %s — %s", failure.test_name.c_str(), failure.reason.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
NSLog(@"Files with failures, permitted or otherwise, were: %@", failures);
|
NSLog(@"Files with failures, permitted or otherwise, were the following %@: %@", @(failures.count), failures);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@interface i8088Tests : XCTestCase
|
// MARK: - Test runners; decoding
|
||||||
@end
|
|
||||||
|
|
||||||
@implementation i8088Tests
|
template <InstructionSet::x86::Model model, InstructionSet::x86::InstructionType type>
|
||||||
|
NSString *to_string(
|
||||||
using Instruction = InstructionSet::x86::Instruction<InstructionSet::x86::InstructionType::Bits16>;
|
const std::pair<int, InstructionSet::x86::Instruction<type>> &instruction,
|
||||||
- (NSString *)
|
int offsetLength,
|
||||||
toString:(const std::pair<int, Instruction> &)instruction
|
int immediateLength
|
||||||
offsetLength:(int)offsetLength
|
) {
|
||||||
immediateLength:(int)immediateLength
|
const auto operation = to_string(instruction, model, offsetLength, immediateLength);
|
||||||
{
|
|
||||||
const auto operation = to_string(instruction, InstructionSet::x86::Model::i8086, offsetLength, immediateLength);
|
|
||||||
return [[NSString stringWithUTF8String:operation.c_str()]
|
return [[NSString stringWithUTF8String:operation.c_str()]
|
||||||
stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
|
stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (bool)applyDecodingTest:(NSDictionary *)test file:(NSString *)file assert:(BOOL)assert {
|
template <InstructionSet::x86::Model model>
|
||||||
InstructionSet::x86::Decoder<InstructionSet::x86::Model::i8086> decoder;
|
bool apply_decoding_test(NSDictionary *test, NSString *file, BOOL assert) {
|
||||||
|
InstructionSet::x86::Decoder<model> decoder;
|
||||||
|
|
||||||
// Build a vector of the instruction bytes; this makes manual step debugging easier.
|
// Build a vector of the instruction bytes; this makes manual step debugging easier.
|
||||||
const auto data = bytes(test[@"bytes"]);
|
const auto data = bytes(test[@"bytes"]);
|
||||||
@@ -603,7 +662,10 @@ using Instruction = InstructionSet::x86::Instruction<InstructionSet::x86::Instru
|
|||||||
};
|
};
|
||||||
|
|
||||||
const auto decoded = decoder.decode(data.data(), data.size());
|
const auto decoded = decoder.decode(data.data(), data.size());
|
||||||
const bool sizeMatched = decoded.first == data.size();
|
const bool sizeMatched =
|
||||||
|
(model == InstructionSet::x86::Model::i8086) ?
|
||||||
|
(decoded.first == data.size()) :
|
||||||
|
(decoded.first == data.size() - 1); // The 80286 instruction set adds a HLT after every instruction.
|
||||||
if(assert) {
|
if(assert) {
|
||||||
XCTAssert(
|
XCTAssert(
|
||||||
sizeMatched,
|
sizeMatched,
|
||||||
@@ -622,12 +684,12 @@ using Instruction = InstructionSet::x86::Instruction<InstructionSet::x86::Instru
|
|||||||
// The decoder doesn't preserve the original offset length, which makes no functional difference but
|
// The decoder doesn't preserve the original offset length, which makes no functional difference but
|
||||||
// does affect the way that offsets are printed in the test set.
|
// does affect the way that offsets are printed in the test set.
|
||||||
NSSet<NSString *> *decodings = [NSSet setWithObjects:
|
NSSet<NSString *> *decodings = [NSSet setWithObjects:
|
||||||
[self toString:decoded offsetLength:4 immediateLength:4],
|
to_string<model>(decoded, 4, 4),
|
||||||
[self toString:decoded offsetLength:2 immediateLength:4],
|
to_string<model>(decoded, 2, 4),
|
||||||
[self toString:decoded offsetLength:0 immediateLength:4],
|
to_string<model>(decoded, 0, 4),
|
||||||
[self toString:decoded offsetLength:4 immediateLength:2],
|
to_string<model>(decoded, 4, 2),
|
||||||
[self toString:decoded offsetLength:2 immediateLength:2],
|
to_string<model>(decoded, 2, 2),
|
||||||
[self toString:decoded offsetLength:0 immediateLength:2],
|
to_string<model>(decoded, 0, 2),
|
||||||
nil];
|
nil];
|
||||||
|
|
||||||
auto compare_decoding = [&](NSString *name) -> bool {
|
auto compare_decoding = [&](NSString *name) -> bool {
|
||||||
@@ -672,36 +734,53 @@ using Instruction = InstructionSet::x86::Instruction<InstructionSet::x86::Instru
|
|||||||
return isEqual;
|
return isEqual;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)printFailures:(NSArray<NSString *> *)failures {
|
template <InstructionSet::x86::Model model>
|
||||||
NSLog(
|
void test_decoding(const char *home) {
|
||||||
@"%ld failures out of %ld tests: %@",
|
|
||||||
failures.count,
|
|
||||||
testFiles(TestSuiteHome8088).count,
|
|
||||||
[failures sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)]);
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)testDecoding {
|
|
||||||
NSMutableArray<NSString *> *failures = [[NSMutableArray alloc] init];
|
NSMutableArray<NSString *> *failures = [[NSMutableArray alloc] init];
|
||||||
for(NSString *file in testFiles(TestSuiteHome8088)) @autoreleasepool {
|
for(NSString *file in test_files(home)) @autoreleasepool {
|
||||||
for(NSDictionary *test in testsInFile(file)) {
|
for(NSDictionary *test in tests_in_file(file)) {
|
||||||
// A single failure per instruction is fine.
|
// A single failure per instruction is fine.
|
||||||
if(![self applyDecodingTest:test file:file assert:YES]) {
|
if(!apply_decoding_test<model>(test, file, YES)) {
|
||||||
[failures addObject:file];
|
[failures addObject:file];
|
||||||
|
|
||||||
// Attempt a second decoding, to provide a debugger hook.
|
// Attempt a second decoding, to provide a debugger hook.
|
||||||
[self applyDecodingTest:test file:file assert:NO];
|
apply_decoding_test<model>(test, file, NO);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[self printFailures:failures];
|
NSLog(
|
||||||
|
@"%ld failures out of %ld tests: %@",
|
||||||
|
failures.count,
|
||||||
|
test_files(TestSuiteHome8088).count,
|
||||||
|
[failures sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@interface i8088Tests : XCTestCase
|
||||||
|
@end
|
||||||
|
|
||||||
|
@implementation i8088Tests
|
||||||
|
|
||||||
|
using Instruction = InstructionSet::x86::Instruction<InstructionSet::x86::InstructionType::Bits16>;
|
||||||
|
|
||||||
|
// MARK: - 8088
|
||||||
|
|
||||||
|
- (void)testDecoding8088 {
|
||||||
|
test_decoding<InstructionSet::x86::Model::i8086>(TestSuiteHome8088);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)testExecution8088 {
|
- (void)testExecution8088 {
|
||||||
test_execution<InstructionSet::x86::Model::i8086>(TestSuiteHome8088);
|
test_execution<InstructionSet::x86::Model::i8086>(TestSuiteHome8088);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - 80286
|
||||||
|
|
||||||
|
- (void)testDecoding80286 {
|
||||||
|
test_decoding<InstructionSet::x86::Model::i80286>(TestSuiteHome80286);
|
||||||
|
}
|
||||||
|
|
||||||
- (void)testExecution80286 {
|
- (void)testExecution80286 {
|
||||||
test_execution<InstructionSet::x86::Model::i80286>(TestSuiteHome80286);
|
test_execution<InstructionSet::x86::Model::i80286>(TestSuiteHome80286);
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user