mirror of
https://github.com/TomHarte/CLK.git
synced 2024-10-15 04:24:26 +00:00
Merge pull request #1193 from TomHarte/8088Intentions
Work towards x86 access violations.
This commit is contained in:
commit
18820644b0
@ -138,7 +138,7 @@ std::pair<int, typename Decoder<model>::InstructionT> Decoder<model>::decode(con
|
|||||||
|
|
||||||
PartialBlock(0x00, ADD); break;
|
PartialBlock(0x00, ADD); break;
|
||||||
case 0x06: Complete(PUSH, ES, None, data_size_); break;
|
case 0x06: Complete(PUSH, ES, None, data_size_); break;
|
||||||
case 0x07: Complete(POP, ES, None, data_size_); break;
|
case 0x07: Complete(POP, None, ES, data_size_); break;
|
||||||
|
|
||||||
PartialBlock(0x08, OR); break;
|
PartialBlock(0x08, OR); break;
|
||||||
case 0x0e: Complete(PUSH, CS, None, data_size_); break;
|
case 0x0e: Complete(PUSH, CS, None, data_size_); break;
|
||||||
@ -147,7 +147,7 @@ std::pair<int, typename Decoder<model>::InstructionT> Decoder<model>::decode(con
|
|||||||
// prefixed with $0f.
|
// prefixed with $0f.
|
||||||
case 0x0f:
|
case 0x0f:
|
||||||
if constexpr (model < Model::i80286) {
|
if constexpr (model < Model::i80286) {
|
||||||
Complete(POP, CS, None, data_size_);
|
Complete(POP, None, CS, data_size_);
|
||||||
} else {
|
} else {
|
||||||
phase_ = Phase::InstructionPageF;
|
phase_ = Phase::InstructionPageF;
|
||||||
}
|
}
|
||||||
@ -155,11 +155,11 @@ std::pair<int, typename Decoder<model>::InstructionT> Decoder<model>::decode(con
|
|||||||
|
|
||||||
PartialBlock(0x10, ADC); break;
|
PartialBlock(0x10, ADC); break;
|
||||||
case 0x16: Complete(PUSH, SS, None, DataSize::Word); break;
|
case 0x16: Complete(PUSH, SS, None, DataSize::Word); break;
|
||||||
case 0x17: Complete(POP, SS, None, DataSize::Word); break;
|
case 0x17: Complete(POP, None, SS, DataSize::Word); break;
|
||||||
|
|
||||||
PartialBlock(0x18, SBB); break;
|
PartialBlock(0x18, SBB); break;
|
||||||
case 0x1e: Complete(PUSH, DS, None, DataSize::Word); break;
|
case 0x1e: Complete(PUSH, DS, None, DataSize::Word); break;
|
||||||
case 0x1f: Complete(POP, DS, None, DataSize::Word); break;
|
case 0x1f: Complete(POP, None, DS, DataSize::Word); break;
|
||||||
|
|
||||||
PartialBlock(0x20, AND); break;
|
PartialBlock(0x20, AND); break;
|
||||||
case 0x26: segment_override_ = Source::ES; break;
|
case 0x26: segment_override_ = Source::ES; break;
|
||||||
@ -523,145 +523,147 @@ std::pair<int, typename Decoder<model>::InstructionT> Decoder<model>::decode(con
|
|||||||
|
|
||||||
// MARK: - Additional F page of instructions.
|
// MARK: - Additional F page of instructions.
|
||||||
|
|
||||||
if(phase_ == Phase::InstructionPageF && source != end) {
|
if constexpr (model >= Model::i80286) {
|
||||||
// Update the instruction acquired.
|
if(phase_ == Phase::InstructionPageF && source != end) {
|
||||||
const uint8_t instr = *source;
|
// Update the instruction acquired.
|
||||||
++source;
|
const uint8_t instr = *source;
|
||||||
++consumed_;
|
++source;
|
||||||
|
++consumed_;
|
||||||
|
|
||||||
// NB: to reach here, the instruction set must be at least
|
// NB: to reach here, the instruction set must be at least
|
||||||
// that of an 80286.
|
// that of an 80286.
|
||||||
switch(instr) {
|
switch(instr) {
|
||||||
default: undefined();
|
default: undefined();
|
||||||
|
|
||||||
case 0x00: MemRegReg(Invalid, MemRegSLDT_to_VERW, data_size_); break;
|
case 0x00: MemRegReg(Invalid, MemRegSLDT_to_VERW, data_size_); break;
|
||||||
case 0x01: MemRegReg(Invalid, MemRegSGDT_to_LMSW, data_size_); break;
|
case 0x01: MemRegReg(Invalid, MemRegSGDT_to_LMSW, data_size_); break;
|
||||||
case 0x02: MemRegReg(LAR, Reg_MemReg, data_size_); break;
|
case 0x02: MemRegReg(LAR, Reg_MemReg, data_size_); break;
|
||||||
case 0x03: MemRegReg(LSL, Reg_MemReg, data_size_); break;
|
case 0x03: MemRegReg(LSL, Reg_MemReg, data_size_); break;
|
||||||
case 0x05:
|
case 0x05:
|
||||||
Requires(i80286);
|
Requires(i80286);
|
||||||
Complete(LOADALL, None, None, DataSize::Byte);
|
Complete(LOADALL, None, None, DataSize::Byte);
|
||||||
break;
|
break;
|
||||||
case 0x06: Complete(CLTS, None, None, DataSize::Byte); break;
|
case 0x06: Complete(CLTS, None, None, DataSize::Byte); break;
|
||||||
|
|
||||||
case 0x20:
|
case 0x20:
|
||||||
RequiresMin(i80386);
|
RequiresMin(i80386);
|
||||||
MemRegReg(MOVfromCr, Reg_MemReg, DataSize::DWord);
|
MemRegReg(MOVfromCr, Reg_MemReg, DataSize::DWord);
|
||||||
break;
|
break;
|
||||||
case 0x21:
|
case 0x21:
|
||||||
RequiresMin(i80386);
|
RequiresMin(i80386);
|
||||||
MemRegReg(MOVfromDr, Reg_MemReg, DataSize::DWord);
|
MemRegReg(MOVfromDr, Reg_MemReg, DataSize::DWord);
|
||||||
break;
|
break;
|
||||||
case 0x22:
|
case 0x22:
|
||||||
RequiresMin(i80386);
|
RequiresMin(i80386);
|
||||||
MemRegReg(MOVtoCr, Reg_MemReg, DataSize::DWord);
|
MemRegReg(MOVtoCr, Reg_MemReg, DataSize::DWord);
|
||||||
break;
|
break;
|
||||||
case 0x23:
|
case 0x23:
|
||||||
RequiresMin(i80386);
|
RequiresMin(i80386);
|
||||||
MemRegReg(MOVtoDr, Reg_MemReg, DataSize::DWord);
|
MemRegReg(MOVtoDr, Reg_MemReg, DataSize::DWord);
|
||||||
break;
|
break;
|
||||||
case 0x24:
|
case 0x24:
|
||||||
RequiresMin(i80386);
|
RequiresMin(i80386);
|
||||||
MemRegReg(MOVfromTr, Reg_MemReg, DataSize::DWord);
|
MemRegReg(MOVfromTr, Reg_MemReg, DataSize::DWord);
|
||||||
break;
|
break;
|
||||||
case 0x26:
|
case 0x26:
|
||||||
RequiresMin(i80386);
|
RequiresMin(i80386);
|
||||||
MemRegReg(MOVtoTr, Reg_MemReg, DataSize::DWord);
|
MemRegReg(MOVtoTr, Reg_MemReg, DataSize::DWord);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 0x70: RequiresMin(i80386); Displacement(JO, data_size_); break;
|
case 0x70: RequiresMin(i80386); Displacement(JO, data_size_); break;
|
||||||
case 0x71: RequiresMin(i80386); Displacement(JNO, data_size_); break;
|
case 0x71: RequiresMin(i80386); Displacement(JNO, data_size_); break;
|
||||||
case 0x72: RequiresMin(i80386); Displacement(JB, data_size_); break;
|
case 0x72: RequiresMin(i80386); Displacement(JB, data_size_); break;
|
||||||
case 0x73: RequiresMin(i80386); Displacement(JNB, data_size_); break;
|
case 0x73: RequiresMin(i80386); Displacement(JNB, data_size_); break;
|
||||||
case 0x74: RequiresMin(i80386); Displacement(JZ, data_size_); break;
|
case 0x74: RequiresMin(i80386); Displacement(JZ, data_size_); break;
|
||||||
case 0x75: RequiresMin(i80386); Displacement(JNZ, data_size_); break;
|
case 0x75: RequiresMin(i80386); Displacement(JNZ, data_size_); break;
|
||||||
case 0x76: RequiresMin(i80386); Displacement(JBE, data_size_); break;
|
case 0x76: RequiresMin(i80386); Displacement(JBE, data_size_); break;
|
||||||
case 0x77: RequiresMin(i80386); Displacement(JNBE, data_size_); break;
|
case 0x77: RequiresMin(i80386); Displacement(JNBE, data_size_); break;
|
||||||
case 0x78: RequiresMin(i80386); Displacement(JS, data_size_); break;
|
case 0x78: RequiresMin(i80386); Displacement(JS, data_size_); break;
|
||||||
case 0x79: RequiresMin(i80386); Displacement(JNS, data_size_); break;
|
case 0x79: RequiresMin(i80386); Displacement(JNS, data_size_); break;
|
||||||
case 0x7a: RequiresMin(i80386); Displacement(JP, data_size_); break;
|
case 0x7a: RequiresMin(i80386); Displacement(JP, data_size_); break;
|
||||||
case 0x7b: RequiresMin(i80386); Displacement(JNP, data_size_); break;
|
case 0x7b: RequiresMin(i80386); Displacement(JNP, data_size_); break;
|
||||||
case 0x7c: RequiresMin(i80386); Displacement(JL, data_size_); break;
|
case 0x7c: RequiresMin(i80386); Displacement(JL, data_size_); break;
|
||||||
case 0x7d: RequiresMin(i80386); Displacement(JNL, data_size_); break;
|
case 0x7d: RequiresMin(i80386); Displacement(JNL, data_size_); break;
|
||||||
case 0x7e: RequiresMin(i80386); Displacement(JLE, data_size_); break;
|
case 0x7e: RequiresMin(i80386); Displacement(JLE, data_size_); break;
|
||||||
case 0x7f: RequiresMin(i80386); Displacement(JNLE, data_size_); break;
|
case 0x7f: RequiresMin(i80386); Displacement(JNLE, data_size_); break;
|
||||||
|
|
||||||
#define Set(x) \
|
#define Set(x) \
|
||||||
RequiresMin(i80386); \
|
RequiresMin(i80386); \
|
||||||
MemRegReg(SET##x, MemRegSingleOperand, DataSize::Byte);
|
MemRegReg(SET##x, MemRegSingleOperand, DataSize::Byte);
|
||||||
|
|
||||||
case 0x90: Set(O); break;
|
case 0x90: Set(O); break;
|
||||||
case 0x91: Set(NO); break;
|
case 0x91: Set(NO); break;
|
||||||
case 0x92: Set(B); break;
|
case 0x92: Set(B); break;
|
||||||
case 0x93: Set(NB); break;
|
case 0x93: Set(NB); break;
|
||||||
case 0x94: Set(Z); break;
|
case 0x94: Set(Z); break;
|
||||||
case 0x95: Set(NZ); break;
|
case 0x95: Set(NZ); break;
|
||||||
case 0x96: Set(BE); break;
|
case 0x96: Set(BE); break;
|
||||||
case 0x97: Set(NBE); break;
|
case 0x97: Set(NBE); break;
|
||||||
case 0x98: Set(S); break;
|
case 0x98: Set(S); break;
|
||||||
case 0x99: Set(NS); break;
|
case 0x99: Set(NS); break;
|
||||||
case 0x9a: Set(P); break;
|
case 0x9a: Set(P); break;
|
||||||
case 0x9b: Set(NP); break;
|
case 0x9b: Set(NP); break;
|
||||||
case 0x9c: Set(L); break;
|
case 0x9c: Set(L); break;
|
||||||
case 0x9d: Set(NL); break;
|
case 0x9d: Set(NL); break;
|
||||||
case 0x9e: Set(LE); break;
|
case 0x9e: Set(LE); break;
|
||||||
case 0x9f: Set(NLE); break;
|
case 0x9f: Set(NLE); break;
|
||||||
|
|
||||||
#undef Set
|
#undef Set
|
||||||
|
|
||||||
case 0xa0: RequiresMin(i80386); Complete(PUSH, FS, None, data_size_); break;
|
case 0xa0: RequiresMin(i80386); Complete(PUSH, FS, None, data_size_); break;
|
||||||
case 0xa1: RequiresMin(i80386); Complete(POP, FS, None, data_size_); break;
|
case 0xa1: RequiresMin(i80386); Complete(POP, None, FS, data_size_); break;
|
||||||
case 0xa3: RequiresMin(i80386); MemRegReg(BT, MemReg_Reg, data_size_); break;
|
case 0xa3: RequiresMin(i80386); MemRegReg(BT, MemReg_Reg, data_size_); break;
|
||||||
case 0xa4:
|
case 0xa4:
|
||||||
RequiresMin(i80386);
|
RequiresMin(i80386);
|
||||||
MemRegReg(SHLDimm, Reg_MemReg, data_size_);
|
MemRegReg(SHLDimm, Reg_MemReg, data_size_);
|
||||||
operand_size_ = DataSize::Byte;
|
operand_size_ = DataSize::Byte;
|
||||||
break;
|
break;
|
||||||
case 0xa5:
|
case 0xa5:
|
||||||
RequiresMin(i80386);
|
RequiresMin(i80386);
|
||||||
MemRegReg(SHLDCL, MemReg_Reg, data_size_);
|
MemRegReg(SHLDCL, MemReg_Reg, data_size_);
|
||||||
break;
|
break;
|
||||||
case 0xa8: RequiresMin(i80386); Complete(PUSH, GS, None, data_size_); break;
|
case 0xa8: RequiresMin(i80386); Complete(PUSH, GS, None, data_size_); break;
|
||||||
case 0xa9: RequiresMin(i80386); Complete(POP, GS, None, data_size_); break;
|
case 0xa9: RequiresMin(i80386); Complete(POP, None, GS, data_size_); break;
|
||||||
case 0xab: RequiresMin(i80386); MemRegReg(BTS, MemReg_Reg, data_size_); break;
|
case 0xab: RequiresMin(i80386); MemRegReg(BTS, MemReg_Reg, data_size_); break;
|
||||||
case 0xac:
|
case 0xac:
|
||||||
RequiresMin(i80386);
|
RequiresMin(i80386);
|
||||||
MemRegReg(SHRDimm, Reg_MemReg, data_size_);
|
MemRegReg(SHRDimm, Reg_MemReg, data_size_);
|
||||||
operand_size_ = DataSize::Byte;
|
operand_size_ = DataSize::Byte;
|
||||||
break;
|
break;
|
||||||
case 0xad:
|
case 0xad:
|
||||||
RequiresMin(i80386);
|
RequiresMin(i80386);
|
||||||
MemRegReg(SHRDCL, MemReg_Reg, data_size_);
|
MemRegReg(SHRDCL, MemReg_Reg, data_size_);
|
||||||
break;
|
break;
|
||||||
case 0xaf:
|
case 0xaf:
|
||||||
RequiresMin(i80386);
|
RequiresMin(i80386);
|
||||||
MemRegReg(IMUL_2, Reg_MemReg, data_size_);
|
MemRegReg(IMUL_2, Reg_MemReg, data_size_);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 0xb2: RequiresMin(i80386); MemRegReg(LSS, Reg_MemReg, data_size_); break;
|
case 0xb2: RequiresMin(i80386); MemRegReg(LSS, Reg_MemReg, data_size_); break;
|
||||||
case 0xb3: RequiresMin(i80386); MemRegReg(BTR, MemReg_Reg, data_size_); break;
|
case 0xb3: RequiresMin(i80386); MemRegReg(BTR, MemReg_Reg, data_size_); break;
|
||||||
case 0xb4: RequiresMin(i80386); MemRegReg(LFS, Reg_MemReg, data_size_); break;
|
case 0xb4: RequiresMin(i80386); MemRegReg(LFS, Reg_MemReg, data_size_); break;
|
||||||
case 0xb5: RequiresMin(i80386); MemRegReg(LGS, Reg_MemReg, data_size_); break;
|
case 0xb5: RequiresMin(i80386); MemRegReg(LGS, Reg_MemReg, data_size_); break;
|
||||||
case 0xb6:
|
case 0xb6:
|
||||||
RequiresMin(i80386);
|
RequiresMin(i80386);
|
||||||
MemRegReg(MOVZX, Reg_MemReg, DataSize::Byte);
|
MemRegReg(MOVZX, Reg_MemReg, DataSize::Byte);
|
||||||
break;
|
break;
|
||||||
case 0xb7:
|
case 0xb7:
|
||||||
RequiresMin(i80386);
|
RequiresMin(i80386);
|
||||||
MemRegReg(MOVZX, Reg_MemReg, DataSize::Word);
|
MemRegReg(MOVZX, Reg_MemReg, DataSize::Word);
|
||||||
break;
|
break;
|
||||||
case 0xba: RequiresMin(i80386); MemRegReg(Invalid, MemRegBT_to_BTC, data_size_); break;
|
case 0xba: RequiresMin(i80386); MemRegReg(Invalid, MemRegBT_to_BTC, data_size_); break;
|
||||||
case 0xbb: RequiresMin(i80386); MemRegReg(BTC, MemReg_Reg, data_size_); break;
|
case 0xbb: RequiresMin(i80386); MemRegReg(BTC, MemReg_Reg, data_size_); break;
|
||||||
case 0xbc: RequiresMin(i80386); MemRegReg(BSF, MemReg_Reg, data_size_); break;
|
case 0xbc: RequiresMin(i80386); MemRegReg(BSF, MemReg_Reg, data_size_); break;
|
||||||
case 0xbd: RequiresMin(i80386); MemRegReg(BSR, MemReg_Reg, data_size_); break;
|
case 0xbd: RequiresMin(i80386); MemRegReg(BSR, MemReg_Reg, data_size_); break;
|
||||||
case 0xbe:
|
case 0xbe:
|
||||||
RequiresMin(i80386);
|
RequiresMin(i80386);
|
||||||
MemRegReg(MOVSX, Reg_MemReg, DataSize::Byte);
|
MemRegReg(MOVSX, Reg_MemReg, DataSize::Byte);
|
||||||
break;
|
break;
|
||||||
case 0xbf:
|
case 0xbf:
|
||||||
RequiresMin(i80386);
|
RequiresMin(i80386);
|
||||||
MemRegReg(MOVSX, Reg_MemReg, DataSize::Word);
|
MemRegReg(MOVSX, Reg_MemReg, DataSize::Word);
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -979,18 +981,20 @@ std::pair<int, typename Decoder<model>::InstructionT> Decoder<model>::decode(con
|
|||||||
|
|
||||||
// MARK: - ScaleIndexBase
|
// MARK: - ScaleIndexBase
|
||||||
|
|
||||||
if(phase_ == Phase::ScaleIndexBase && source != end) {
|
if constexpr (is_32bit(model)) {
|
||||||
sib_ = *source;
|
if(phase_ == Phase::ScaleIndexBase && source != end) {
|
||||||
++source;
|
sib_ = *source;
|
||||||
++consumed_;
|
++source;
|
||||||
|
++consumed_;
|
||||||
|
|
||||||
// Potentially record the lack of a base.
|
// Potentially record the lack of a base.
|
||||||
if(displacement_size_ == DataSize::None && (uint8_t(sib_)&7) == 5) {
|
if(displacement_size_ == DataSize::None && (uint8_t(sib_)&7) == 5) {
|
||||||
source_ = (source_ == Source::Indirect) ? Source::IndirectNoBase : source_;
|
source_ = (source_ == Source::Indirect) ? Source::IndirectNoBase : source_;
|
||||||
destination_ = (destination_ == Source::Indirect) ? Source::IndirectNoBase : destination_;
|
destination_ = (destination_ == Source::Indirect) ? Source::IndirectNoBase : destination_;
|
||||||
|
}
|
||||||
|
|
||||||
|
phase_ = (displacement_size_ != DataSize::None || operand_size_ != DataSize::None) ? Phase::DisplacementOrOperand : Phase::ReadyToPost;
|
||||||
}
|
}
|
||||||
|
|
||||||
phase_ = (displacement_size_ != DataSize::None || operand_size_ != DataSize::None) ? Phase::DisplacementOrOperand : Phase::ReadyToPost;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Displacement and operand.
|
// MARK: - Displacement and operand.
|
||||||
@ -1041,6 +1045,18 @@ std::pair<int, typename Decoder<model>::InstructionT> Decoder<model>::decode(con
|
|||||||
// MARK: - Check for completion.
|
// MARK: - Check for completion.
|
||||||
|
|
||||||
if(phase_ == Phase::ReadyToPost) {
|
if(phase_ == Phase::ReadyToPost) {
|
||||||
|
// TODO: map to #UD where applicable; build LOCK into the Operation type, buying an extra bit for the operation?
|
||||||
|
//
|
||||||
|
// As of the P6 Intel stipulates that:
|
||||||
|
//
|
||||||
|
// "The LOCK prefix can be prepended only to the following instructions and to those forms of the instructions
|
||||||
|
// that use a memory operand: ADD, ADC, AND, BTC, BTR, BTS, CMPXCHG, DEC, INC, NEG, NOT, OR, SBB, SUB, XOR,
|
||||||
|
// XADD, and XCHG."
|
||||||
|
//
|
||||||
|
// ... and the #UD exception will be raised if LOCK is encountered elsewhere. So adding 17 additional
|
||||||
|
// operations would unlock an extra bit of storage for a net gain of 239 extra operation types and thereby
|
||||||
|
// alleviating any concerns over whether there'll be space to handle MMX, floating point extensions, etc.
|
||||||
|
|
||||||
const auto result = std::make_pair(
|
const auto result = std::make_pair(
|
||||||
consumed_,
|
consumed_,
|
||||||
InstructionT(
|
InstructionT(
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -163,7 +163,7 @@ enum class Operation: uint8_t {
|
|||||||
XOR,
|
XOR,
|
||||||
/// NOP; no further fields.
|
/// NOP; no further fields.
|
||||||
NOP,
|
NOP,
|
||||||
/// POP from the stack to source.
|
/// POP from the stack to destination.
|
||||||
POP,
|
POP,
|
||||||
/// POP from the stack to the flags register.
|
/// POP from the stack to the flags register.
|
||||||
POPF,
|
POPF,
|
||||||
|
@ -15,24 +15,59 @@
|
|||||||
|
|
||||||
namespace InstructionSet::x86 {
|
namespace InstructionSet::x86 {
|
||||||
|
|
||||||
|
/// Explains the type of access that `perform` intends to perform; is provided as a template parameter to whatever
|
||||||
|
/// the caller supplies as `MemoryT` and `RegistersT` when obtaining a reference to whatever the processor
|
||||||
|
/// intends to reference.
|
||||||
|
///
|
||||||
|
/// `perform` guarantees to validate all accesses before modifying any state, giving the caller opportunity to generate
|
||||||
|
/// any exceptions that might be applicable.
|
||||||
|
enum class AccessType {
|
||||||
|
/// The requested value will be read from.
|
||||||
|
Read,
|
||||||
|
/// The requested value will be written to.
|
||||||
|
Write,
|
||||||
|
/// The requested value will be read from and then written to.
|
||||||
|
ReadModifyWrite,
|
||||||
|
/// The requested value has already been authorised for whatever form of access is now intended, so there's no
|
||||||
|
/// need further to inspect. This is done e.g. by operations that will push multiple values to the stack to verify that
|
||||||
|
/// all necessary stack space is available ahead of pushing anything, though each individual push will then result in
|
||||||
|
/// a further `Preauthorised` access.
|
||||||
|
PreauthorisedRead,
|
||||||
|
PreauthorisedWrite,
|
||||||
|
};
|
||||||
|
|
||||||
|
template <
|
||||||
|
Model model_,
|
||||||
|
typename FlowControllerT,
|
||||||
|
typename RegistersT,
|
||||||
|
typename MemoryT,
|
||||||
|
typename IOT
|
||||||
|
> struct ExecutionContext {
|
||||||
|
FlowControllerT flow_controller;
|
||||||
|
Status status;
|
||||||
|
RegistersT registers;
|
||||||
|
MemoryT memory;
|
||||||
|
IOT io;
|
||||||
|
static constexpr Model model = model_;
|
||||||
|
};
|
||||||
|
|
||||||
/// Performs @c instruction querying @c registers and/or @c memory as required, using @c io for port input/output,
|
/// Performs @c instruction querying @c registers and/or @c memory as required, using @c io for port input/output,
|
||||||
/// and providing any flow control effects to @c flow_controller.
|
/// and providing any flow control effects to @c flow_controller.
|
||||||
///
|
///
|
||||||
/// Any change in processor status will be applied to @c status.
|
/// Any change in processor status will be applied to @c status.
|
||||||
template <
|
template <
|
||||||
Model model,
|
|
||||||
typename InstructionT,
|
typename InstructionT,
|
||||||
typename FlowControllerT,
|
typename ContextT
|
||||||
typename RegistersT,
|
|
||||||
typename MemoryT,
|
|
||||||
typename IOT
|
|
||||||
> void perform(
|
> void perform(
|
||||||
const InstructionT &instruction,
|
const InstructionT &instruction,
|
||||||
Status &status,
|
ContextT &context
|
||||||
FlowControllerT &flow_controller,
|
);
|
||||||
RegistersT ®isters,
|
|
||||||
MemoryT &memory,
|
template <
|
||||||
IOT &io
|
typename ContextT
|
||||||
|
> void interrupt(
|
||||||
|
int index,
|
||||||
|
ContextT &context
|
||||||
);
|
);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -14,6 +14,8 @@
|
|||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <unordered_set>
|
||||||
|
|
||||||
#include "NSData+dataWithContentsOfGZippedFile.h"
|
#include "NSData+dataWithContentsOfGZippedFile.h"
|
||||||
|
|
||||||
@ -91,110 +93,193 @@ struct Registers {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
struct Memory {
|
struct Memory {
|
||||||
enum class Tag {
|
public:
|
||||||
Seeded,
|
using AccessType = InstructionSet::x86::AccessType;
|
||||||
AccessExpected,
|
|
||||||
Accessed,
|
|
||||||
FlagsL,
|
|
||||||
FlagsH
|
|
||||||
};
|
|
||||||
|
|
||||||
std::unordered_map<uint32_t, Tag> tags;
|
template <typename IntT, AccessType type> struct ReturnType;
|
||||||
std::vector<uint8_t> memory;
|
|
||||||
const Registers ®isters_;
|
|
||||||
|
|
||||||
Memory(Registers ®isters) : registers_(registers) {
|
// Reads: return a value directly.
|
||||||
memory.resize(1024*1024);
|
template <typename IntT> struct ReturnType<IntT, AccessType::Read> { using type = IntT; };
|
||||||
}
|
template <typename IntT> struct ReturnType<IntT, AccessType::PreauthorisedRead> { using type = IntT; };
|
||||||
|
|
||||||
void clear() {
|
// Writes: return a reference.
|
||||||
tags.clear();
|
template <typename IntT> struct ReturnType<IntT, AccessType::Write> { using type = IntT &; };
|
||||||
}
|
template <typename IntT> struct ReturnType<IntT, AccessType::ReadModifyWrite> { using type = IntT &; };
|
||||||
|
template <typename IntT> struct ReturnType<IntT, AccessType::PreauthorisedWrite> { using type = IntT &; };
|
||||||
|
|
||||||
void seed(uint32_t address, uint8_t value) {
|
// Constructor.
|
||||||
memory[address] = value;
|
Memory(Registers ®isters) : registers_(registers) {
|
||||||
tags[address] = Tag::Seeded;
|
memory.resize(1024*1024);
|
||||||
}
|
|
||||||
|
|
||||||
void touch(uint32_t address) {
|
|
||||||
tags[address] = Tag::AccessExpected;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t segment_base(InstructionSet::x86::Source segment) {
|
|
||||||
uint32_t physical_address;
|
|
||||||
using Source = InstructionSet::x86::Source;
|
|
||||||
switch(segment) {
|
|
||||||
default: physical_address = registers_.ds_; break;
|
|
||||||
case Source::ES: physical_address = registers_.es_; break;
|
|
||||||
case Source::CS: physical_address = registers_.cs_; break;
|
|
||||||
case Source::SS: physical_address = registers_.ss_; break;
|
|
||||||
}
|
}
|
||||||
return physical_address << 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Entry point used by the flow controller so that it can mark up locations at which the flags were written,
|
// Initialisation.
|
||||||
// so that defined-flag-only masks can be applied while verifying RAM contents.
|
void clear() {
|
||||||
template <typename IntT> IntT &access(InstructionSet::x86::Source segment, uint16_t address, Tag tag) {
|
tags.clear();
|
||||||
const uint32_t physical_address = (segment_base(segment) + address) & 0xf'ffff;
|
}
|
||||||
return access<IntT>(physical_address, tag);
|
|
||||||
}
|
|
||||||
|
|
||||||
// An additional entry point for the flow controller; on the original 8086 interrupt vectors aren't relative
|
void seed(uint32_t address, uint8_t value) {
|
||||||
// to a selector, they're just at an absolute location.
|
memory[address] = value;
|
||||||
template <typename IntT> IntT &access(uint32_t address, Tag tag) {
|
tags[address] = Tag::Seeded;
|
||||||
// Check for address wraparound
|
}
|
||||||
if(address >= 0x10'0001 - sizeof(IntT)) {
|
|
||||||
if constexpr (std::is_same_v<IntT, uint8_t>) {
|
void touch(uint32_t address) {
|
||||||
address &= 0xf'ffff;
|
tags[address] = Tag::AccessExpected;
|
||||||
} else {
|
}
|
||||||
if(address == 0xf'ffff) {
|
|
||||||
// This is a 16-bit access comprising the final byte in memory and the first.
|
// Preauthorisation call-ins.
|
||||||
write_back_address_[0] = address;
|
void preauthorise_stack_write(uint32_t length) {
|
||||||
write_back_address_[1] = 0;
|
uint16_t sp = registers_.sp_;
|
||||||
|
while(length--) {
|
||||||
|
--sp;
|
||||||
|
preauthorise(InstructionSet::x86::Source::SS, sp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void preauthorise_stack_read(uint32_t length) {
|
||||||
|
uint16_t sp = registers_.sp_;
|
||||||
|
while(length--) {
|
||||||
|
preauthorise(InstructionSet::x86::Source::SS, sp);
|
||||||
|
++sp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void preauthorise_read(InstructionSet::x86::Source segment, uint16_t start, uint32_t length) {
|
||||||
|
while(length--) {
|
||||||
|
preauthorise(segment, start);
|
||||||
|
++start;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void preauthorise_read(uint32_t start, uint32_t length) {
|
||||||
|
while(length--) {
|
||||||
|
preauthorise(start);
|
||||||
|
++start;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Access call-ins.
|
||||||
|
|
||||||
|
// Accesses an address based on segment:offset.
|
||||||
|
template <typename IntT, AccessType type>
|
||||||
|
typename ReturnType<IntT, type>::type &access(InstructionSet::x86::Source segment, uint32_t address) {
|
||||||
|
if constexpr (std::is_same_v<IntT, uint16_t>) {
|
||||||
|
// If this is a 16-bit access that runs past the end of the segment, it'll wrap back
|
||||||
|
// to the start. So the 16-bit value will need to be a local cache.
|
||||||
|
if(address == 0xffff) {
|
||||||
|
write_back_address_[0] = (segment_base(segment) + address) & 0xf'ffff;
|
||||||
|
write_back_address_[1] = (write_back_address_[0] - 65535) & 0xf'ffff;
|
||||||
write_back_value_ = memory[write_back_address_[0]] | (memory[write_back_address_[1]] << 8);
|
write_back_value_ = memory[write_back_address_[0]] | (memory[write_back_address_[1]] << 8);
|
||||||
return write_back_value_;
|
return write_back_value_;
|
||||||
} else {
|
}
|
||||||
address &= 0xf'ffff;
|
}
|
||||||
|
auto &value = access<IntT, type>(segment, address, Tag::Accessed);
|
||||||
|
|
||||||
|
// If the CPU has indicated a write, it should be safe to fuzz the value now.
|
||||||
|
if(type == AccessType::Write) {
|
||||||
|
value = IntT(~0);
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Accesses an address based on physical location.
|
||||||
|
template <typename IntT, AccessType type>
|
||||||
|
typename ReturnType<IntT, type>::type &access(uint32_t address) {
|
||||||
|
return access<IntT, type>(address, Tag::Accessed);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename IntT>
|
||||||
|
void write_back() {
|
||||||
|
if constexpr (std::is_same_v<IntT, uint16_t>) {
|
||||||
|
if(write_back_address_[0] != NoWriteBack) {
|
||||||
|
memory[write_back_address_[0]] = write_back_value_ & 0xff;
|
||||||
|
memory[write_back_address_[1]] = write_back_value_ >> 8;
|
||||||
|
write_back_address_[0] = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(tags.find(address) == tags.end()) {
|
private:
|
||||||
printf("Access to unexpected RAM address");
|
enum class Tag {
|
||||||
}
|
Seeded,
|
||||||
tags[address] = tag;
|
AccessExpected,
|
||||||
return *reinterpret_cast<IntT *>(&memory[address]);
|
Accessed,
|
||||||
}
|
};
|
||||||
|
|
||||||
// Entry point for the 8086; simply notes that memory was accessed.
|
std::unordered_set<uint32_t> preauthorisations;
|
||||||
template <typename IntT> IntT &access([[maybe_unused]] InstructionSet::x86::Source segment, uint32_t address) {
|
std::unordered_map<uint32_t, Tag> tags;
|
||||||
if constexpr (std::is_same_v<IntT, uint16_t>) {
|
std::vector<uint8_t> memory;
|
||||||
// If this is a 16-bit access that runs past the end of the segment, it'll wrap back
|
const Registers ®isters_;
|
||||||
// to the start. So the 16-bit value will need to be a local cache.
|
|
||||||
if(address == 0xffff) {
|
void preauthorise(uint32_t address) {
|
||||||
write_back_address_[0] = (segment_base(segment) + address) & 0xf'ffff;
|
preauthorisations.insert(address);
|
||||||
write_back_address_[1] = (write_back_address_[0] - 65535) & 0xf'ffff;
|
}
|
||||||
write_back_value_ = memory[write_back_address_[0]] | (memory[write_back_address_[1]] << 8);
|
void preauthorise(InstructionSet::x86::Source segment, uint16_t address) {
|
||||||
return write_back_value_;
|
preauthorise((segment_base(segment) + address) & 0xf'ffff);
|
||||||
|
}
|
||||||
|
bool test_preauthorisation(uint32_t address) {
|
||||||
|
auto authorisation = preauthorisations.find(address);
|
||||||
|
if(authorisation == preauthorisations.end()) {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
preauthorisations.erase(authorisation);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
return access<IntT>(segment, address, Tag::Accessed);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename IntT>
|
uint32_t segment_base(InstructionSet::x86::Source segment) {
|
||||||
void write_back() {
|
uint32_t physical_address;
|
||||||
if constexpr (std::is_same_v<IntT, uint16_t>) {
|
using Source = InstructionSet::x86::Source;
|
||||||
if(write_back_address_[0] != NoWriteBack) {
|
switch(segment) {
|
||||||
memory[write_back_address_[0]] = write_back_value_ & 0xff;
|
default: physical_address = registers_.ds_; break;
|
||||||
memory[write_back_address_[1]] = write_back_value_ >> 8;
|
case Source::ES: physical_address = registers_.es_; break;
|
||||||
write_back_address_[0] = 0;
|
case Source::CS: physical_address = registers_.cs_; break;
|
||||||
|
case Source::SS: physical_address = registers_.ss_; break;
|
||||||
}
|
}
|
||||||
|
return physical_address << 4;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
static constexpr uint32_t NoWriteBack = 0; // A low byte address of 0 can't require write-back.
|
|
||||||
uint32_t write_back_address_[2] = {NoWriteBack, NoWriteBack};
|
// Entry point used by the flow controller so that it can mark up locations at which the flags were written,
|
||||||
uint16_t write_back_value_;
|
// so that defined-flag-only masks can be applied while verifying RAM contents.
|
||||||
|
template <typename IntT, AccessType type>
|
||||||
|
typename ReturnType<IntT, type>::type &access(InstructionSet::x86::Source segment, uint16_t address, Tag tag) {
|
||||||
|
const uint32_t physical_address = (segment_base(segment) + address) & 0xf'ffff;
|
||||||
|
return access<IntT, type>(physical_address, tag);
|
||||||
|
}
|
||||||
|
|
||||||
|
// An additional entry point for the flow controller; on the original 8086 interrupt vectors aren't relative
|
||||||
|
// to a selector, they're just at an absolute location.
|
||||||
|
template <typename IntT, AccessType type>
|
||||||
|
typename ReturnType<IntT, type>::type &access(uint32_t address, Tag tag) {
|
||||||
|
if constexpr (type == AccessType::PreauthorisedRead || type == AccessType::PreauthorisedWrite) {
|
||||||
|
if(!test_preauthorisation(address)) {
|
||||||
|
printf("Non preauthorised access\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for address wraparound
|
||||||
|
if(address > 0x10'0000 - sizeof(IntT)) {
|
||||||
|
if constexpr (std::is_same_v<IntT, uint8_t>) {
|
||||||
|
address &= 0xf'ffff;
|
||||||
|
} else {
|
||||||
|
address &= 0xf'ffff;
|
||||||
|
if(address == 0xf'ffff) {
|
||||||
|
// This is a 16-bit access comprising the final byte in memory and the first.
|
||||||
|
write_back_address_[0] = address;
|
||||||
|
write_back_address_[1] = 0;
|
||||||
|
write_back_value_ = memory[write_back_address_[0]] | (memory[write_back_address_[1]] << 8);
|
||||||
|
return write_back_value_;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(tags.find(address) == tags.end()) {
|
||||||
|
printf("Access to unexpected RAM address\n");
|
||||||
|
}
|
||||||
|
tags[address] = tag;
|
||||||
|
return *reinterpret_cast<IntT *>(&memory[address]);
|
||||||
|
}
|
||||||
|
|
||||||
|
static constexpr uint32_t NoWriteBack = 0; // A low byte address of 0 can't require write-back.
|
||||||
|
uint32_t write_back_address_[2] = {NoWriteBack, NoWriteBack};
|
||||||
|
uint16_t write_back_value_;
|
||||||
};
|
};
|
||||||
struct IO {
|
struct IO {
|
||||||
template <typename IntT> void out([[maybe_unused]] uint16_t port, [[maybe_unused]] IntT value) {}
|
template <typename IntT> void out([[maybe_unused]] uint16_t port, [[maybe_unused]] IntT value) {}
|
||||||
@ -205,39 +290,7 @@ class FlowController {
|
|||||||
FlowController(Memory &memory, Registers ®isters, Status &status) :
|
FlowController(Memory &memory, Registers ®isters, Status &status) :
|
||||||
memory_(memory), registers_(registers), status_(status) {}
|
memory_(memory), registers_(registers), status_(status) {}
|
||||||
|
|
||||||
void did_iret() {}
|
// Requirements for perform.
|
||||||
void did_near_ret() {}
|
|
||||||
void did_far_ret() {}
|
|
||||||
|
|
||||||
void interrupt(int index) {
|
|
||||||
const uint16_t address = static_cast<uint16_t>(index) << 2;
|
|
||||||
const uint16_t new_ip = memory_.access<uint16_t>(address, Memory::Tag::Accessed);
|
|
||||||
const uint16_t new_cs = memory_.access<uint16_t>(address + 2, Memory::Tag::Accessed);
|
|
||||||
|
|
||||||
push(status_.get(), true);
|
|
||||||
|
|
||||||
using Flag = InstructionSet::x86::Flag;
|
|
||||||
status_.set_from<Flag::Interrupt, Flag::Trap>(0);
|
|
||||||
|
|
||||||
// Push CS and IP.
|
|
||||||
push(registers_.cs_);
|
|
||||||
push(registers_.ip_);
|
|
||||||
|
|
||||||
registers_.cs_ = new_cs;
|
|
||||||
registers_.ip_ = new_ip;
|
|
||||||
}
|
|
||||||
|
|
||||||
void call(uint16_t address) {
|
|
||||||
push(registers_.ip_);
|
|
||||||
jump(address);
|
|
||||||
}
|
|
||||||
|
|
||||||
void call(uint16_t segment, uint16_t offset) {
|
|
||||||
push(registers_.cs_);
|
|
||||||
push(registers_.ip_);
|
|
||||||
jump(segment, offset);
|
|
||||||
}
|
|
||||||
|
|
||||||
void jump(uint16_t address) {
|
void jump(uint16_t address) {
|
||||||
registers_.ip_ = address;
|
registers_.ip_ = address;
|
||||||
}
|
}
|
||||||
@ -250,12 +303,14 @@ class FlowController {
|
|||||||
void halt() {}
|
void halt() {}
|
||||||
void wait() {}
|
void wait() {}
|
||||||
|
|
||||||
void begin_instruction() {
|
|
||||||
should_repeat_ = false;
|
|
||||||
}
|
|
||||||
void repeat_last() {
|
void repeat_last() {
|
||||||
should_repeat_ = true;
|
should_repeat_ = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Other actions.
|
||||||
|
void begin_instruction() {
|
||||||
|
should_repeat_ = false;
|
||||||
|
}
|
||||||
bool should_repeat() const {
|
bool should_repeat() const {
|
||||||
return should_repeat_;
|
return should_repeat_;
|
||||||
}
|
}
|
||||||
@ -265,23 +320,6 @@ class FlowController {
|
|||||||
Registers ®isters_;
|
Registers ®isters_;
|
||||||
Status &status_;
|
Status &status_;
|
||||||
bool should_repeat_ = false;
|
bool should_repeat_ = false;
|
||||||
|
|
||||||
void push(uint16_t value, bool is_flags = false) {
|
|
||||||
// Perform the push in two steps because it's possible for SP to underflow, and so that FlagsL and
|
|
||||||
// FlagsH can be set separately.
|
|
||||||
--registers_.sp_;
|
|
||||||
memory_.access<uint8_t>(
|
|
||||||
InstructionSet::x86::Source::SS,
|
|
||||||
registers_.sp_,
|
|
||||||
is_flags ? Memory::Tag::FlagsH : Memory::Tag::Accessed
|
|
||||||
) = value >> 8;
|
|
||||||
--registers_.sp_;
|
|
||||||
memory_.access<uint8_t>(
|
|
||||||
InstructionSet::x86::Source::SS,
|
|
||||||
registers_.sp_,
|
|
||||||
is_flags ? Memory::Tag::FlagsL : Memory::Tag::Accessed
|
|
||||||
) = value & 0xff;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct ExecutionSupport {
|
struct ExecutionSupport {
|
||||||
@ -290,8 +328,9 @@ struct ExecutionSupport {
|
|||||||
Memory memory;
|
Memory memory;
|
||||||
FlowController flow_controller;
|
FlowController flow_controller;
|
||||||
IO io;
|
IO io;
|
||||||
|
static constexpr auto model = InstructionSet::x86::Model::i8086;
|
||||||
|
|
||||||
ExecutionSupport() : memory(registers), flow_controller(memory, registers, status) {}
|
ExecutionSupport(): memory(registers), flow_controller(memory, registers, status) {}
|
||||||
|
|
||||||
void clear() {
|
void clear() {
|
||||||
memory.clear();
|
memory.clear();
|
||||||
@ -310,8 +349,8 @@ struct FailedExecution {
|
|||||||
@end
|
@end
|
||||||
|
|
||||||
@implementation i8088Tests {
|
@implementation i8088Tests {
|
||||||
ExecutionSupport execution_support;
|
|
||||||
std::vector<FailedExecution> execution_failures;
|
std::vector<FailedExecution> execution_failures;
|
||||||
|
ExecutionSupport execution_support;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSArray<NSString *> *)testFiles {
|
- (NSArray<NSString *> *)testFiles {
|
||||||
@ -383,6 +422,8 @@ struct FailedExecution {
|
|||||||
return hexInstruction;
|
return hexInstruction;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
EACCES;
|
||||||
|
|
||||||
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 = decoded.first == data.size();
|
||||||
if(assert) {
|
if(assert) {
|
||||||
@ -522,13 +563,9 @@ struct FailedExecution {
|
|||||||
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();
|
||||||
InstructionSet::x86::perform<InstructionSet::x86::Model::i8086>(
|
InstructionSet::x86::perform(
|
||||||
decoded.second,
|
decoded.second,
|
||||||
execution_support.status,
|
execution_support
|
||||||
execution_support.flow_controller,
|
|
||||||
execution_support.registers,
|
|
||||||
execution_support.memory,
|
|
||||||
execution_support.io
|
|
||||||
);
|
);
|
||||||
} while (execution_support.flow_controller.should_repeat());
|
} while (execution_support.flow_controller.should_repeat());
|
||||||
|
|
||||||
@ -537,21 +574,32 @@ struct FailedExecution {
|
|||||||
InstructionSet::x86::Status intended_status;
|
InstructionSet::x86::Status intended_status;
|
||||||
|
|
||||||
bool ramEqual = true;
|
bool ramEqual = true;
|
||||||
|
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 = execution_support.memory.access<uint8_t, Memory::AccessType::Read>(address);
|
||||||
|
|
||||||
uint8_t mask = 0xff;
|
if((mask_position != 1) && value == [ram[1] intValue]) {
|
||||||
if(const auto tag = execution_support.memory.tags.find(address); tag != execution_support.memory.tags.end()) {
|
continue;
|
||||||
switch(tag->second) {
|
}
|
||||||
default: break;
|
|
||||||
case Memory::Tag::FlagsH: mask = flags_mask >> 8; break;
|
// Consider whether this apparent mismatch might be because flags have been written to memory;
|
||||||
case Memory::Tag::FlagsL: mask = flags_mask & 0xff; break;
|
// allow only one use of the [16-bit] mask per test.
|
||||||
|
bool matched_with_mask = false;
|
||||||
|
while(mask_position < 2) {
|
||||||
|
const uint8_t mask = mask_position ? (flags_mask >> 8) : (flags_mask & 0xff);
|
||||||
|
++mask_position;
|
||||||
|
if((value & mask) == ([ram[1] intValue] & mask)) {
|
||||||
|
matched_with_mask = true;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if(matched_with_mask) {
|
||||||
if((execution_support.memory.memory[address] & mask) != ([ram[1] intValue] & mask)) {
|
continue;
|
||||||
ramEqual = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ramEqual = false;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
[self populate:intended_registers status:intended_status value:final_state[@"regs"]];
|
[self populate:intended_registers status:intended_status value:final_state[@"regs"]];
|
||||||
@ -663,7 +711,15 @@ struct FailedExecution {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Lock in current failure rate.
|
// Lock in current failure rate.
|
||||||
XCTAssertLessThanOrEqual(execution_failures.size(), 1654);
|
XCTAssertLessThanOrEqual(execution_failures.size(), 4138);
|
||||||
|
|
||||||
|
// Current accepted failures:
|
||||||
|
// * 65 instances of DAA with invalid BCD input, and 64 of DAS;
|
||||||
|
// * 2484 instances of LEA from a register, which officially has undefined results;
|
||||||
|
// * 42 instances of AAM 00h for which I can't figure out what to do with flags; and
|
||||||
|
// * 1486 instances of IDIV, most either with a rep or repne that on the 8086 specifically negatives the result,
|
||||||
|
// but some admittedly still unexplained (primarily setting overflow even though the result doesn't overflow;
|
||||||
|
// a couple of online 8086 emulators also didn't throw so maybe this is an 8086 quirk?)
|
||||||
|
|
||||||
for(const auto &failure: execution_failures) {
|
for(const auto &failure: execution_failures) {
|
||||||
NSLog(@"Failed %s — %s", failure.test_name.c_str(), failure.reason.c_str());
|
NSLog(@"Failed %s — %s", failure.test_name.c_str(), failure.reason.c_str());
|
||||||
|
Loading…
Reference in New Issue
Block a user