mirror of
https://github.com/TomHarte/CLK.git
synced 2025-02-16 18:30:32 +00:00
Merge pull request #1171 from TomHarte/8088Execution
Add first seeds of x86 execution.
This commit is contained in:
commit
167b52c4ff
@ -9,6 +9,7 @@
|
||||
#ifndef InstructionSets_M68k_PerformImplementation_h
|
||||
#define InstructionSets_M68k_PerformImplementation_h
|
||||
|
||||
#include "../../../Numeric/Carry.hpp"
|
||||
#include "../ExceptionVectors.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
@ -25,28 +26,6 @@ inline int32_t s_extend16(uint16_t x) { return int32_t(int16_t(x)); }
|
||||
|
||||
namespace Primitive {
|
||||
|
||||
/// @returns An int of type @c IntT with only the most-significant bit set.
|
||||
template <typename IntT> constexpr IntT top_bit() {
|
||||
static_assert(!std::numeric_limits<IntT>::is_signed);
|
||||
constexpr IntT max = std::numeric_limits<IntT>::max();
|
||||
return max - (max >> 1);
|
||||
}
|
||||
|
||||
/// @returns An int with the top bit indicating whether overflow occurred when @c source and @c destination
|
||||
/// were either added (if @c is_add is true) or subtracted (if @c is_add is false) and the result was @c result.
|
||||
/// All other bits will be clear.
|
||||
template <bool is_add, typename IntT>
|
||||
static Status::FlagT overflow(IntT source, IntT destination, IntT result) {
|
||||
const IntT output_changed = result ^ destination;
|
||||
const IntT input_differed = source ^ destination;
|
||||
|
||||
if constexpr (is_add) {
|
||||
return top_bit<IntT>() & output_changed & ~input_differed;
|
||||
} else {
|
||||
return top_bit<IntT>() & output_changed & input_differed;
|
||||
}
|
||||
}
|
||||
|
||||
/// Performs an add or subtract (as per @c is_add) between @c source and @c destination,
|
||||
/// updating @c status. @c is_extend indicates whether this is an extend operation (e.g. ADDX)
|
||||
/// or a plain one (e.g. ADD).
|
||||
@ -81,7 +60,7 @@ static void add_sub(IntT source, IntT &destination, Status &status) {
|
||||
status.zero_result = Status::FlagT(result);
|
||||
}
|
||||
status.set_negative(result);
|
||||
status.overflow_flag = overflow<is_add>(source, destination, result);
|
||||
status.overflow_flag = Numeric::overflow<is_add>(destination, source, result);
|
||||
destination = result;
|
||||
}
|
||||
|
||||
@ -143,7 +122,7 @@ void compare(IntT source, IntT destination, Status &status) {
|
||||
const IntT result = destination - source;
|
||||
status.carry_flag = result > destination;
|
||||
status.set_neg_zero(result);
|
||||
status.overflow_flag = Primitive::overflow<false>(source, destination, result);
|
||||
status.overflow_flag = Numeric::overflow<false>(destination, source, result);
|
||||
}
|
||||
|
||||
/// @returns the name of the bit to be used as a mask for BCLR, BCHG, BSET or BTST for
|
||||
@ -293,7 +272,7 @@ template <bool is_extend, typename IntT> void negative(IntT &source, Status &sta
|
||||
}
|
||||
status.extend_flag = status.carry_flag = result; // i.e. any value other than 0 will result in carry.
|
||||
status.set_negative(result);
|
||||
status.overflow_flag = Primitive::overflow<false>(source, IntT(0), result);
|
||||
status.overflow_flag = Numeric::overflow<false>(IntT(0), source, result);
|
||||
|
||||
source = result;
|
||||
}
|
||||
@ -311,11 +290,6 @@ template <typename IntT, typename FlowController> int shift_count(uint8_t source
|
||||
return count;
|
||||
}
|
||||
|
||||
/// @returns The number of bits in @c IntT.
|
||||
template <typename IntT> constexpr int bit_size() {
|
||||
return sizeof(IntT) * 8;
|
||||
}
|
||||
|
||||
/// Perform an arithmetic or logical shift, i.e. any of LSL, LSR, ASL or ASR.
|
||||
template <Operation operation, typename IntT, typename FlowController> void shift(uint32_t source, IntT &destination, Status &status, FlowController &flow_controller) {
|
||||
static_assert(
|
||||
@ -325,7 +299,7 @@ template <Operation operation, typename IntT, typename FlowController> void shif
|
||||
operation == Operation::LSRb || operation == Operation::LSRw || operation == Operation::LSRl
|
||||
);
|
||||
|
||||
constexpr auto size = bit_size<IntT>();
|
||||
constexpr auto size = Numeric::bit_size<IntT>();
|
||||
const auto shift = shift_count<IntT>(uint8_t(source), flow_controller);
|
||||
|
||||
if(!shift) {
|
||||
@ -355,7 +329,7 @@ template <Operation operation, typename IntT, typename FlowController> void shif
|
||||
if(shift > size) {
|
||||
status.carry_flag = status.extend_flag = 0;
|
||||
} else {
|
||||
status.carry_flag = status.extend_flag = (destination << (shift - 1)) & top_bit<IntT>();
|
||||
status.carry_flag = status.extend_flag = (destination << (shift - 1)) & Numeric::top_bit<IntT>();
|
||||
}
|
||||
|
||||
if(type == Type::LSL) {
|
||||
@ -370,7 +344,7 @@ template <Operation operation, typename IntT, typename FlowController> void shif
|
||||
// For a shift of n places, overflow will be set if the top n+1 bits were not
|
||||
// all the same value.
|
||||
const auto affected_bits = IntT(
|
||||
~((top_bit<IntT>() >> shift) - 1)
|
||||
~((Numeric::top_bit<IntT>() >> shift) - 1)
|
||||
); // e.g. shift = 1 => ~((0x80 >> 1) - 1) = ~(0x40 - 1) = ~0x3f = 0xc0, i.e. if shift is
|
||||
// 1 then the top two bits are relevant to whether there was overflow. If they have the
|
||||
// same value, i.e. are both 0 or are both 1, then there wasn't. Otherwise there was.
|
||||
@ -396,7 +370,7 @@ template <Operation operation, typename IntT, typename FlowController> void shif
|
||||
|
||||
const IntT sign_word =
|
||||
type == Type::LSR ?
|
||||
0 : (destination & top_bit<IntT>() ? IntT(~0) : 0);
|
||||
0 : (destination & Numeric::top_bit<IntT>() ? IntT(~0) : 0);
|
||||
|
||||
if(shift >= size) {
|
||||
destination = sign_word;
|
||||
@ -417,7 +391,7 @@ template <Operation operation, typename IntT, typename FlowController> void rota
|
||||
operation == Operation::RORb || operation == Operation::RORw || operation == Operation::RORl
|
||||
);
|
||||
|
||||
constexpr auto size = bit_size<IntT>();
|
||||
constexpr auto size = Numeric::bit_size<IntT>();
|
||||
auto shift = shift_count<IntT>(uint8_t(source), flow_controller);
|
||||
|
||||
if(!shift) {
|
||||
@ -442,7 +416,7 @@ template <Operation operation, typename IntT, typename FlowController> void rota
|
||||
(destination << (size - shift))
|
||||
);
|
||||
}
|
||||
status.carry_flag = Status::FlagT(destination & top_bit<IntT>());
|
||||
status.carry_flag = Status::FlagT(destination & Numeric::top_bit<IntT>());
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -458,7 +432,7 @@ template <Operation operation, typename IntT, typename FlowController> void rox(
|
||||
operation == Operation::ROXRb || operation == Operation::ROXRw || operation == Operation::ROXRl
|
||||
);
|
||||
|
||||
constexpr auto size = bit_size<IntT>();
|
||||
constexpr auto size = Numeric::bit_size<IntT>();
|
||||
auto shift = shift_count<IntT>(uint8_t(source), flow_controller) % (size + 1);
|
||||
|
||||
if(!shift) {
|
||||
@ -469,9 +443,9 @@ template <Operation operation, typename IntT, typename FlowController> void rox(
|
||||
case Operation::ROXLb: case Operation::ROXLw: case Operation::ROXLl:
|
||||
status.carry_flag = Status::FlagT((destination >> (size - shift)) & 1);
|
||||
|
||||
if(shift == bit_size<IntT>()) {
|
||||
if(shift == Numeric::bit_size<IntT>()) {
|
||||
destination = IntT(
|
||||
(status.extend_flag ? top_bit<IntT>() : 0) |
|
||||
(status.extend_flag ? Numeric::top_bit<IntT>() : 0) |
|
||||
(destination >> 1)
|
||||
);
|
||||
} else if(shift == 1) {
|
||||
@ -491,7 +465,7 @@ template <Operation operation, typename IntT, typename FlowController> void rox(
|
||||
case Operation::ROXRb: case Operation::ROXRw: case Operation::ROXRl:
|
||||
status.carry_flag = Status::FlagT(destination & (1 << (shift - 1)));
|
||||
|
||||
if(shift == bit_size<IntT>()) {
|
||||
if(shift == Numeric::bit_size<IntT>()) {
|
||||
destination = IntT(
|
||||
(status.extend_flag ? 1 : 0) |
|
||||
(destination << 1)
|
||||
@ -499,12 +473,12 @@ template <Operation operation, typename IntT, typename FlowController> void rox(
|
||||
} else if(shift == 1) {
|
||||
destination = IntT(
|
||||
(destination >> 1) |
|
||||
(status.extend_flag ? top_bit<IntT>() : 0)
|
||||
(status.extend_flag ? Numeric::top_bit<IntT>() : 0)
|
||||
);
|
||||
} else {
|
||||
destination = IntT(
|
||||
(destination >> shift) |
|
||||
((status.extend_flag ? top_bit<IntT>() : 0) >> (shift - 1)) |
|
||||
((status.extend_flag ? Numeric::top_bit<IntT>() : 0) >> (shift - 1)) |
|
||||
(destination << (size + 1 - shift))
|
||||
);
|
||||
}
|
||||
@ -882,14 +856,14 @@ template <
|
||||
Shifts and rotates.
|
||||
*/
|
||||
case Operation::ASLm:
|
||||
status.extend_flag = status.carry_flag = src.w & Primitive::top_bit<uint16_t>();
|
||||
status.overflow_flag = (src.w ^ (src.w << 1)) & Primitive::top_bit<uint16_t>();
|
||||
status.extend_flag = status.carry_flag = src.w & Numeric::top_bit<uint16_t>();
|
||||
status.overflow_flag = (src.w ^ (src.w << 1)) & Numeric::top_bit<uint16_t>();
|
||||
src.w <<= 1;
|
||||
status.set_neg_zero(src.w);
|
||||
break;
|
||||
|
||||
case Operation::LSLm:
|
||||
status.extend_flag = status.carry_flag = src.w & Primitive::top_bit<uint16_t>();
|
||||
status.extend_flag = status.carry_flag = src.w & Numeric::top_bit<uint16_t>();
|
||||
status.overflow_flag = 0;
|
||||
src.w <<= 1;
|
||||
status.set_neg_zero(src.w);
|
||||
@ -898,7 +872,7 @@ template <
|
||||
case Operation::ASRm:
|
||||
status.extend_flag = status.carry_flag = src.w & 1;
|
||||
status.overflow_flag = 0;
|
||||
src.w = (src.w & Primitive::top_bit<uint16_t>()) | (src.w >> 1);
|
||||
src.w = (src.w & Numeric::top_bit<uint16_t>()) | (src.w >> 1);
|
||||
status.set_neg_zero(src.w);
|
||||
break;
|
||||
|
||||
@ -918,13 +892,13 @@ template <
|
||||
|
||||
case Operation::RORm:
|
||||
src.w = uint16_t((src.w >> 1) | (src.w << 15));
|
||||
status.carry_flag = src.w & Primitive::top_bit<uint16_t>();
|
||||
status.carry_flag = src.w & Numeric::top_bit<uint16_t>();
|
||||
status.overflow_flag = 0;
|
||||
status.set_neg_zero(src.w);
|
||||
break;
|
||||
|
||||
case Operation::ROXLm:
|
||||
status.carry_flag = src.w & Primitive::top_bit<uint16_t>();
|
||||
status.carry_flag = src.w & Numeric::top_bit<uint16_t>();
|
||||
src.w = uint16_t((src.w << 1) | (status.extend_flag ? 0x0001 : 0x0000));
|
||||
status.extend_flag = status.carry_flag;
|
||||
status.overflow_flag = 0;
|
||||
|
@ -1,318 +0,0 @@
|
||||
//
|
||||
// DataPointerResolver.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 24/02/2022.
|
||||
// Copyright © 2022 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef DataPointerResolver_hpp
|
||||
#define DataPointerResolver_hpp
|
||||
|
||||
#include "Instruction.hpp"
|
||||
#include "Model.hpp"
|
||||
|
||||
#include <cassert>
|
||||
|
||||
namespace InstructionSet::x86 {
|
||||
|
||||
/// Unlike source, describes only registers, and breaks
|
||||
/// them down by conventional name — so AL, AH, AX and EAX are all
|
||||
/// listed separately and uniquely, rather than being eAX+size or
|
||||
/// eSPorAH with a size of 1.
|
||||
enum class Register: uint8_t {
|
||||
// 8-bit registers.
|
||||
AL, AH,
|
||||
CL, CH,
|
||||
DL, DH,
|
||||
BL, BH,
|
||||
|
||||
// 16-bit registers.
|
||||
AX, CX, DX, BX,
|
||||
SP, BP, SI, DI,
|
||||
ES, CS, SS, DS,
|
||||
FS, GS,
|
||||
|
||||
// 32-bit registers.
|
||||
EAX, ECX, EDX, EBX,
|
||||
ESP, EBP, ESI, EDI,
|
||||
|
||||
//
|
||||
None
|
||||
};
|
||||
|
||||
/// @returns @c true if @c r is the same size as @c DataT; @c false otherwise.
|
||||
/// @discussion Provided primarily to aid in asserts; if the decoder and resolver are both
|
||||
/// working then it shouldn't be necessary to test this in register files.
|
||||
template <typename DataT> constexpr bool is_sized(Register r) {
|
||||
static_assert(sizeof(DataT) == 4 || sizeof(DataT) == 2 || sizeof(DataT) == 1);
|
||||
|
||||
if constexpr (sizeof(DataT) == 4) {
|
||||
return r >= Register::EAX && r < Register::None;
|
||||
}
|
||||
|
||||
if constexpr (sizeof(DataT) == 2) {
|
||||
return r >= Register::AX && r < Register::EAX;
|
||||
}
|
||||
|
||||
if constexpr (sizeof(DataT) == 1) {
|
||||
return r >= Register::AL && r < Register::AX;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// @returns the proper @c Register given @c source and data of size @c sizeof(DataT),
|
||||
/// or Register::None if no such register exists (e.g. asking for a 32-bit version of CS).
|
||||
template <typename DataT> constexpr Register register_for_source(Source source) {
|
||||
static_assert(sizeof(DataT) == 4 || sizeof(DataT) == 2 || sizeof(DataT) == 1);
|
||||
|
||||
if constexpr (sizeof(DataT) == 4) {
|
||||
switch(source) {
|
||||
case Source::eAX: return Register::EAX;
|
||||
case Source::eCX: return Register::ECX;
|
||||
case Source::eDX: return Register::EDX;
|
||||
case Source::eBX: return Register::EBX;
|
||||
case Source::eSPorAH: return Register::ESP;
|
||||
case Source::eBPorCH: return Register::EBP;
|
||||
case Source::eSIorDH: return Register::ESI;
|
||||
case Source::eDIorBH: return Register::EDI;
|
||||
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
if constexpr (sizeof(DataT) == 2) {
|
||||
switch(source) {
|
||||
case Source::eAX: return Register::AX;
|
||||
case Source::eCX: return Register::CX;
|
||||
case Source::eDX: return Register::DX;
|
||||
case Source::eBX: return Register::BX;
|
||||
case Source::eSPorAH: return Register::SP;
|
||||
case Source::eBPorCH: return Register::BP;
|
||||
case Source::eSIorDH: return Register::SI;
|
||||
case Source::eDIorBH: return Register::DI;
|
||||
case Source::ES: return Register::ES;
|
||||
case Source::CS: return Register::CS;
|
||||
case Source::SS: return Register::SS;
|
||||
case Source::DS: return Register::DS;
|
||||
case Source::FS: return Register::FS;
|
||||
case Source::GS: return Register::GS;
|
||||
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
if constexpr (sizeof(DataT) == 1) {
|
||||
switch(source) {
|
||||
case Source::eAX: return Register::AL;
|
||||
case Source::eCX: return Register::CL;
|
||||
case Source::eDX: return Register::DL;
|
||||
case Source::eBX: return Register::BL;
|
||||
case Source::eSPorAH: return Register::AH;
|
||||
case Source::eBPorCH: return Register::CH;
|
||||
case Source::eSIorDH: return Register::DH;
|
||||
case Source::eDIorBH: return Register::BH;
|
||||
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
return Register::None;
|
||||
}
|
||||
|
||||
/// Reads from or writes to the source or target identified by a DataPointer, relying upon two user-supplied classes:
|
||||
///
|
||||
/// * a register bank; and
|
||||
/// * a memory pool.
|
||||
///
|
||||
/// The register bank should implement `template<typename DataT, Register> DataT read()` and `template<typename DataT, Register> void write(DataT)`.
|
||||
/// Those functions will be called only with registers and data types that are appropriate to the @c model.
|
||||
///
|
||||
/// The memory pool should implement `template<typename DataT> DataT read(Source segment, uint32_t address)` and
|
||||
/// `template<typename DataT> void write(Source segment, uint32_t address, DataT value)`.
|
||||
template <Model model, typename RegistersT, typename MemoryT> class DataPointerResolver {
|
||||
public:
|
||||
public:
|
||||
/// Reads the data pointed to by @c pointer, referencing @c instruction, @c memory and @c registers as necessary.
|
||||
template <typename DataT> static DataT read(
|
||||
RegistersT ®isters,
|
||||
MemoryT &memory,
|
||||
const Instruction<is_32bit(model)> &instruction,
|
||||
DataPointer pointer);
|
||||
|
||||
/// Writes @c value to the data pointed to by @c pointer, referencing @c instruction, @c memory and @c registers as necessary.
|
||||
template <typename DataT> static void write(
|
||||
RegistersT ®isters,
|
||||
MemoryT &memory,
|
||||
const Instruction<is_32bit(model)> &instruction,
|
||||
DataPointer pointer,
|
||||
DataT value);
|
||||
|
||||
/// Computes the effective address of @c pointer including any displacement applied by @c instruction.
|
||||
/// @c pointer must be of type Source::Indirect.
|
||||
template <bool obscured_indirectNoBase = true, bool has_base = true>
|
||||
static uint32_t effective_address(
|
||||
RegistersT ®isters,
|
||||
const Instruction<is_32bit(model)> &instruction,
|
||||
DataPointer pointer);
|
||||
|
||||
private:
|
||||
template <bool is_write, typename DataT> static void access(
|
||||
RegistersT ®isters,
|
||||
MemoryT &memory,
|
||||
const Instruction<is_32bit(model)> &instruction,
|
||||
DataPointer pointer,
|
||||
DataT &value);
|
||||
};
|
||||
|
||||
|
||||
//
|
||||
// Implementation begins here.
|
||||
//
|
||||
|
||||
template <Model model, typename RegistersT, typename MemoryT>
|
||||
template <typename DataT> DataT DataPointerResolver<model, RegistersT, MemoryT>::read(
|
||||
RegistersT ®isters,
|
||||
MemoryT &memory,
|
||||
const Instruction<is_32bit(model)> &instruction,
|
||||
DataPointer pointer) {
|
||||
DataT result;
|
||||
access<false>(registers, memory, instruction, pointer, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
template <Model model, typename RegistersT, typename MemoryT>
|
||||
template <typename DataT> void DataPointerResolver<model, RegistersT, MemoryT>::write(
|
||||
RegistersT ®isters,
|
||||
MemoryT &memory,
|
||||
const Instruction<is_32bit(model)> &instruction,
|
||||
DataPointer pointer,
|
||||
DataT value) {
|
||||
access<true>(registers, memory, instruction, pointer, value);
|
||||
}
|
||||
|
||||
#define rw(v, r, is_write) \
|
||||
case Source::r: \
|
||||
using VType = typename std::remove_reference<decltype(v)>::type; \
|
||||
if constexpr (is_write) { \
|
||||
registers.template write<VType, register_for_source<VType>(Source::r)>(v); \
|
||||
} else { \
|
||||
v = registers.template read<VType, register_for_source<VType>(Source::r)>(); \
|
||||
} \
|
||||
break;
|
||||
|
||||
#define ALLREGS(v, i) rw(v, eAX, i); rw(v, eCX, i); \
|
||||
rw(v, eDX, i); rw(v, eBX, i); \
|
||||
rw(v, eSPorAH, i); rw(v, eBPorCH, i); \
|
||||
rw(v, eSIorDH, i); rw(v, eDIorBH, i); \
|
||||
rw(v, ES, i); rw(v, CS, i); \
|
||||
rw(v, SS, i); rw(v, DS, i); \
|
||||
rw(v, FS, i); rw(v, GS, i);
|
||||
|
||||
template <Model model, typename RegistersT, typename MemoryT>
|
||||
template <bool obscured_indirectNoBase, bool has_base>
|
||||
uint32_t DataPointerResolver<model, RegistersT, MemoryT>::effective_address(
|
||||
RegistersT ®isters,
|
||||
const Instruction<is_32bit(model)> &instruction,
|
||||
DataPointer pointer) {
|
||||
using AddressT = typename Instruction<is_32bit(model)>::AddressT;
|
||||
AddressT base = 0, index = 0;
|
||||
|
||||
if constexpr (has_base) {
|
||||
switch(pointer.base<obscured_indirectNoBase>()) {
|
||||
default: break;
|
||||
ALLREGS(base, false);
|
||||
}
|
||||
}
|
||||
|
||||
switch(pointer.index()) {
|
||||
default: break;
|
||||
ALLREGS(index, false);
|
||||
}
|
||||
|
||||
uint32_t address = index;
|
||||
if constexpr (model >= Model::i80386) {
|
||||
address <<= pointer.scale();
|
||||
} else {
|
||||
assert(!pointer.scale());
|
||||
}
|
||||
|
||||
// Always compute address as 32-bit.
|
||||
// TODO: verify use of memory_mask around here.
|
||||
// Also I think possibly an exception is supposed to be generated
|
||||
// if the programmer is in 32-bit mode and has asked for 16-bit
|
||||
// address computation but generated e.g. a 17-bit result. Look into
|
||||
// that when working on execution. For now the goal is merely decoding
|
||||
// and this code exists both to verify the presence of all necessary
|
||||
// fields and to help to explore the best breakdown of storage
|
||||
// within Instruction.
|
||||
constexpr uint32_t memory_masks[] = {0x0000'ffff, 0xffff'ffff};
|
||||
const uint32_t memory_mask = memory_masks[int(instruction.address_size())];
|
||||
address = (address & memory_mask) + (base & memory_mask) + instruction.displacement();
|
||||
return address;
|
||||
}
|
||||
|
||||
template <Model model, typename RegistersT, typename MemoryT>
|
||||
template <bool is_write, typename DataT> void DataPointerResolver<model, RegistersT, MemoryT>::access(
|
||||
RegistersT ®isters,
|
||||
MemoryT &memory,
|
||||
const Instruction<is_32bit(model)> &instruction,
|
||||
DataPointer pointer,
|
||||
DataT &value) {
|
||||
const Source source = pointer.source<false>();
|
||||
|
||||
switch(source) {
|
||||
default:
|
||||
if constexpr (!is_write) {
|
||||
value = 0;
|
||||
}
|
||||
return;
|
||||
|
||||
ALLREGS(value, is_write);
|
||||
|
||||
case Source::DirectAddress:
|
||||
if constexpr(is_write) {
|
||||
memory.template write(instruction.data_segment(), instruction.displacement(), value);
|
||||
} else {
|
||||
value = memory.template read<DataT>(instruction.data_segment(), instruction.displacement());
|
||||
}
|
||||
break;
|
||||
case Source::Immediate:
|
||||
value = DataT(instruction.operand());
|
||||
break;
|
||||
|
||||
#define indirect(has_base) { \
|
||||
const auto address = effective_address<false, has_base> \
|
||||
(registers, instruction, pointer); \
|
||||
\
|
||||
if constexpr (is_write) { \
|
||||
memory.template write( \
|
||||
instruction.data_segment(), \
|
||||
address, \
|
||||
value \
|
||||
); \
|
||||
} else { \
|
||||
value = memory.template read<DataT>( \
|
||||
instruction.data_segment(), \
|
||||
address \
|
||||
); \
|
||||
} \
|
||||
}
|
||||
case Source::IndirectNoBase:
|
||||
indirect(false);
|
||||
break;
|
||||
|
||||
case Source::Indirect:
|
||||
indirect(true);
|
||||
break;
|
||||
#undef indirect
|
||||
|
||||
}
|
||||
}
|
||||
#undef ALLREGS
|
||||
#undef rw
|
||||
|
||||
}
|
||||
|
||||
#endif /* DataPointerResolver_hpp */
|
@ -80,7 +80,7 @@ std::pair<int, typename Decoder<model>::InstructionT> Decoder<model>::decode(con
|
||||
#define Displacement(op, size) \
|
||||
SetOperation(Operation::op); \
|
||||
phase_ = Phase::DisplacementOrOperand; \
|
||||
displacement_size_ = size
|
||||
operation_size_= displacement_size_ = size
|
||||
|
||||
/// Handles PUSH [immediate], etc — anything with only an immediate operand.
|
||||
#define Immediate(op, size) \
|
||||
@ -90,11 +90,11 @@ std::pair<int, typename Decoder<model>::InstructionT> Decoder<model>::decode(con
|
||||
operand_size_ = size
|
||||
|
||||
/// Handles far CALL and far JMP — fixed four or six byte operand operations.
|
||||
#define Far(op) \
|
||||
SetOperation(Operation::op); \
|
||||
phase_ = Phase::DisplacementOrOperand; \
|
||||
operand_size_ = DataSize::Word; \
|
||||
destination_ = Source::Immediate; \
|
||||
#define Far(op) \
|
||||
SetOperation(Operation::op); \
|
||||
phase_ = Phase::DisplacementOrOperand; \
|
||||
operation_size_ = operand_size_ = DataSize::Word; \
|
||||
destination_ = Source::Immediate; \
|
||||
displacement_size_ = data_size(default_address_size_)
|
||||
|
||||
/// Handles ENTER — a fixed three-byte operation.
|
||||
@ -353,7 +353,7 @@ std::pair<int, typename Decoder<model>::InstructionT> Decoder<model>::decode(con
|
||||
case 0x8e: MemRegReg(MOV, Seg_MemReg, DataSize::Word); break;
|
||||
case 0x8f: MemRegReg(POP, MemRegSingleOperand, data_size_); break;
|
||||
|
||||
case 0x90: Complete(NOP, None, None, DataSize::None); break; // Or XCHG AX, AX?
|
||||
case 0x90: Complete(NOP, None, None, DataSize::Byte); break; // Could be encoded as XCHG AX, AX if Operation space becomes limited.
|
||||
case 0x91: Complete(XCHG, eAX, eCX, data_size_); break;
|
||||
case 0x92: Complete(XCHG, eAX, eDX, data_size_); break;
|
||||
case 0x93: Complete(XCHG, eAX, eBX, data_size_); break;
|
||||
@ -365,7 +365,7 @@ std::pair<int, typename Decoder<model>::InstructionT> Decoder<model>::decode(con
|
||||
case 0x98: Complete(CBW, eAX, AH, data_size_); break;
|
||||
case 0x99: Complete(CWD, eAX, eDX, data_size_); break;
|
||||
case 0x9a: Far(CALLfar); break;
|
||||
case 0x9b: Complete(WAIT, None, None, DataSize::None); break;
|
||||
case 0x9b: Complete(WAIT, None, None, DataSize::Byte); break;
|
||||
case 0x9c: Complete(PUSHF, None, None, data_size_); break;
|
||||
case 0x9d: Complete(POPF, None, None, data_size_); break;
|
||||
case 0x9e: Complete(SAHF, None, None, DataSize::Byte); break;
|
||||
@ -421,11 +421,11 @@ std::pair<int, typename Decoder<model>::InstructionT> Decoder<model>::decode(con
|
||||
source_ = Source::Immediate;
|
||||
operand_size_ = data_size_;
|
||||
} else {
|
||||
Complete(RETnear, None, None, DataSize::None);
|
||||
Complete(RETnear, None, None, DataSize::Byte);
|
||||
}
|
||||
break;
|
||||
case 0xc2: RegData(RETnear, None, data_size_); break;
|
||||
case 0xc3: Complete(RETnear, None, None, DataSize::None); break;
|
||||
case 0xc3: Complete(RETnear, None, None, DataSize::Byte); break;
|
||||
case 0xc4: MemRegReg(LES, Reg_MemReg, data_size_); break;
|
||||
case 0xc5: MemRegReg(LDS, Reg_MemReg, data_size_); break;
|
||||
case 0xc6: MemRegReg(MOV, MemRegMOV, DataSize::Byte); break;
|
||||
@ -440,14 +440,14 @@ std::pair<int, typename Decoder<model>::InstructionT> Decoder<model>::decode(con
|
||||
break;
|
||||
case 0xc9:
|
||||
if constexpr (model >= Model::i80186) {
|
||||
Complete(LEAVE, None, None, DataSize::None);
|
||||
Complete(LEAVE, None, None, DataSize::Byte);
|
||||
} else {
|
||||
Complete(RETfar, None, None, DataSize::DWord);
|
||||
Complete(RETfar, None, None, DataSize::Word);
|
||||
}
|
||||
break;
|
||||
|
||||
case 0xca: RegData(RETfar, None, data_size_); break;
|
||||
case 0xcb: Complete(RETfar, None, None, DataSize::DWord); break;
|
||||
case 0xcb: Complete(RETfar, None, None, DataSize::Word); break;
|
||||
|
||||
case 0xcc:
|
||||
// Encode INT3 as though it were INT with an
|
||||
@ -456,8 +456,8 @@ std::pair<int, typename Decoder<model>::InstructionT> Decoder<model>::decode(con
|
||||
operand_ = 3;
|
||||
break;
|
||||
case 0xcd: RegData(INT, None, DataSize::Byte); break;
|
||||
case 0xce: Complete(INTO, None, None, DataSize::None); break;
|
||||
case 0xcf: Complete(IRET, None, None, DataSize::None); break;
|
||||
case 0xce: Complete(INTO, None, None, DataSize::Byte); break;
|
||||
case 0xcf: Complete(IRET, None, None, DataSize::Byte); break;
|
||||
|
||||
case 0xd0: case 0xd1:
|
||||
ShiftGroup();
|
||||
@ -505,17 +505,17 @@ std::pair<int, typename Decoder<model>::InstructionT> Decoder<model>::decode(con
|
||||
case 0xf2: repetition_ = Repetition::RepNE; break;
|
||||
case 0xf3: repetition_ = Repetition::RepE; break;
|
||||
|
||||
case 0xf4: Complete(HLT, None, None, DataSize::None); break;
|
||||
case 0xf5: Complete(CMC, None, None, DataSize::None); break;
|
||||
case 0xf4: Complete(HLT, None, None, DataSize::Byte); break;
|
||||
case 0xf5: Complete(CMC, None, None, DataSize::Byte); break;
|
||||
case 0xf6: MemRegReg(Invalid, MemRegTEST_to_IDIV, DataSize::Byte); break;
|
||||
case 0xf7: MemRegReg(Invalid, MemRegTEST_to_IDIV, data_size_); break;
|
||||
|
||||
case 0xf8: Complete(CLC, None, None, DataSize::None); break;
|
||||
case 0xf9: Complete(STC, None, None, DataSize::None); break;
|
||||
case 0xfa: Complete(CLI, None, None, DataSize::None); break;
|
||||
case 0xfb: Complete(STI, None, None, DataSize::None); break;
|
||||
case 0xfc: Complete(CLD, None, None, DataSize::None); break;
|
||||
case 0xfd: Complete(STD, None, None, DataSize::None); break;
|
||||
case 0xf8: Complete(CLC, None, None, DataSize::Byte); break;
|
||||
case 0xf9: Complete(STC, None, None, DataSize::Byte); break;
|
||||
case 0xfa: Complete(CLI, None, None, DataSize::Byte); break;
|
||||
case 0xfb: Complete(STI, None, None, DataSize::Byte); break;
|
||||
case 0xfc: Complete(CLD, None, None, DataSize::Byte); break;
|
||||
case 0xfd: Complete(STD, None, None, DataSize::Byte); break;
|
||||
|
||||
case 0xfe: MemRegReg(Invalid, MemRegINC_DEC, DataSize::Byte); break;
|
||||
case 0xff: MemRegReg(Invalid, MemRegINC_to_PUSH, data_size_); break;
|
||||
@ -541,7 +541,7 @@ std::pair<int, typename Decoder<model>::InstructionT> Decoder<model>::decode(con
|
||||
case 0x03: MemRegReg(LSL, Reg_MemReg, data_size_); break;
|
||||
case 0x05:
|
||||
Requires(i80286);
|
||||
Complete(LOADALL, None, None, DataSize::None);
|
||||
Complete(LOADALL, None, None, DataSize::Byte);
|
||||
break;
|
||||
case 0x06: Complete(CLTS, None, None, DataSize::Byte); break;
|
||||
|
||||
@ -885,7 +885,6 @@ std::pair<int, typename Decoder<model>::InstructionT> Decoder<model>::decode(con
|
||||
case 4: SetOperation(Operation::JMPabs); break;
|
||||
case 5: SetOperation(Operation::JMPfar); break;
|
||||
}
|
||||
// TODO: CALLfar and JMPfar aren't correct above; find out what is.
|
||||
break;
|
||||
|
||||
case ModRegRMFormat::MemRegSingleOperand:
|
||||
@ -1015,23 +1014,11 @@ std::pair<int, typename Decoder<model>::InstructionT> Decoder<model>::decode(con
|
||||
if(bytes_to_consume == outstanding_bytes) {
|
||||
phase_ = Phase::ReadyToPost;
|
||||
|
||||
// TODO: whether the displacement is signed appears to depend on the opcode.
|
||||
// Find an appropriate table.
|
||||
|
||||
if(!sign_extend_displacement_) {
|
||||
switch(displacement_size_) {
|
||||
case DataSize::None: displacement_ = 0; break;
|
||||
case DataSize::Byte: displacement_ = decltype(displacement_)(uint8_t(inward_data_)); break;
|
||||
case DataSize::Word: displacement_ = decltype(displacement_)(uint16_t(inward_data_)); break;
|
||||
case DataSize::DWord: displacement_ = decltype(displacement_)(uint32_t(inward_data_)); break;
|
||||
}
|
||||
} else {
|
||||
switch(displacement_size_) {
|
||||
case DataSize::None: displacement_ = 0; break;
|
||||
case DataSize::Byte: displacement_ = int8_t(inward_data_); break;
|
||||
case DataSize::Word: displacement_ = int16_t(inward_data_); break;
|
||||
case DataSize::DWord: displacement_ = int32_t(inward_data_); break;
|
||||
}
|
||||
switch(displacement_size_) {
|
||||
case DataSize::None: displacement_ = 0; break;
|
||||
case DataSize::Byte: displacement_ = int8_t(inward_data_); break;
|
||||
case DataSize::Word: displacement_ = int16_t(inward_data_); break;
|
||||
case DataSize::DWord: displacement_ = int32_t(inward_data_); break;
|
||||
}
|
||||
inward_data_ >>= bit_size(displacement_size_);
|
||||
|
||||
@ -1066,7 +1053,7 @@ std::pair<int, typename Decoder<model>::InstructionT> Decoder<model>::decode(con
|
||||
address_size_,
|
||||
segment_override_,
|
||||
repetition_,
|
||||
DataSize(operation_size_),
|
||||
operation_size_,
|
||||
static_cast<typename InstructionT::DisplacementT>(displacement_),
|
||||
static_cast<typename InstructionT::ImmediateT>(operand_),
|
||||
consumed_
|
||||
|
@ -195,8 +195,6 @@ template <Model model> class Decoder {
|
||||
|
||||
bool sign_extend_operand_ = false; // If set then sign extend the operand up to the operation size;
|
||||
// otherwise it'll be zero-padded.
|
||||
bool sign_extend_displacement_ = false; // Much as above; 'displacement' is used internally for both
|
||||
// displacements and offsets, so signage will vary.
|
||||
|
||||
// Prefix capture fields.
|
||||
Repetition repetition_ = Repetition::None;
|
||||
@ -225,7 +223,6 @@ template <Model model> class Decoder {
|
||||
next_inward_data_shift_ = 0;
|
||||
inward_data_ = 0;
|
||||
sign_extend_operand_ = false;
|
||||
sign_extend_displacement_ = false;
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -224,7 +224,7 @@
|
||||
|
||||
<td colspan=6>AND</td>
|
||||
<td rowspan=2>SEG =ES</td>
|
||||
<td rowspan=2>POP ES</td>
|
||||
<td rowspan=2>DAA</td>
|
||||
<td colspan=6>SUB</td>
|
||||
<td rowspan=2>SEG =CS</td>
|
||||
<td rowspan=2>DAS</td>
|
||||
|
1872
InstructionSets/x86/Implementation/PerformImplementation.hpp
Normal file
1872
InstructionSets/x86/Implementation/PerformImplementation.hpp
Normal file
File diff suppressed because it is too large
Load Diff
@ -8,6 +8,8 @@
|
||||
|
||||
#include "Instruction.hpp"
|
||||
|
||||
#include "../../Numeric/Carry.hpp"
|
||||
|
||||
#include <cassert>
|
||||
#include <iomanip>
|
||||
#include <sstream>
|
||||
@ -321,6 +323,13 @@ std::string to_hex(int value, int digits, bool with_suffix = true) {
|
||||
return stream.str();
|
||||
};
|
||||
|
||||
template <typename IntT>
|
||||
std::string to_hex(IntT value) {
|
||||
auto stream = std::stringstream();
|
||||
stream << std::uppercase << std::hex << +value << 'h';
|
||||
return stream.str();
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
template <bool is_32bit>
|
||||
@ -335,42 +344,42 @@ std::string InstructionSet::x86::to_string(
|
||||
|
||||
std::string operand;
|
||||
|
||||
auto append = [](std::stringstream &stream, auto value, int length, const char *prefix) {
|
||||
auto append = [](std::stringstream &stream, auto value, int length) {
|
||||
switch(length) {
|
||||
case 0:
|
||||
if(!value) {
|
||||
break;
|
||||
return;
|
||||
}
|
||||
[[fallthrough]];
|
||||
|
||||
case 2:
|
||||
// If asked to pretend the offset was originally two digits then either of: an unsigned
|
||||
// 8-bit value or a sign-extended 8-bit value as having been originally 8-bit.
|
||||
//
|
||||
// This kicks the issue of whether sign was extended appropriately to functionality tests.
|
||||
if(
|
||||
!(value & 0xff00) ||
|
||||
((value & 0xff80) == 0xff80) ||
|
||||
((value & 0xff80) == 0x0000)
|
||||
) {
|
||||
stream << prefix << to_hex(value, 2);
|
||||
break;
|
||||
}
|
||||
[[fallthrough]];
|
||||
default:
|
||||
stream << prefix << to_hex(value, 4);
|
||||
break;
|
||||
value &= 0xff;
|
||||
break;
|
||||
}
|
||||
|
||||
stream << std::uppercase << std::hex << value << 'h';
|
||||
};
|
||||
|
||||
auto append_signed = [](std::stringstream &stream, auto value, int length) {
|
||||
if(!value && !length) {
|
||||
return;
|
||||
}
|
||||
|
||||
const bool is_negative = Numeric::top_bit<decltype(value)>() & value;
|
||||
const uint64_t abs_value = std::abs(int16_t(value)); // TODO: don't assume 16-bit.
|
||||
|
||||
stream << (is_negative ? '-' : '+') << std::uppercase << std::hex << abs_value << 'h';
|
||||
};
|
||||
|
||||
using Source = InstructionSet::x86::Source;
|
||||
const Source source = pointer.source<false>();
|
||||
const Source source = pointer.source();
|
||||
switch(source) {
|
||||
// to_string handles all direct register names correctly.
|
||||
default: return InstructionSet::x86::to_string(source, operation_size);
|
||||
|
||||
case Source::Immediate: {
|
||||
std::stringstream stream;
|
||||
append(stream, instruction.operand(), immediate_length, "");
|
||||
append(stream, instruction.operand(), immediate_length);
|
||||
return stream.str();
|
||||
}
|
||||
|
||||
@ -383,7 +392,8 @@ std::string InstructionSet::x86::to_string(
|
||||
stream << InstructionSet::x86::to_string(operation_size) << ' ';
|
||||
}
|
||||
|
||||
Source segment = instruction.data_segment();
|
||||
stream << '[';
|
||||
Source segment = instruction.segment_override();
|
||||
if(segment == Source::None) {
|
||||
segment = pointer.default_segment();
|
||||
if(segment == Source::None) {
|
||||
@ -392,7 +402,6 @@ std::string InstructionSet::x86::to_string(
|
||||
}
|
||||
stream << InstructionSet::x86::to_string(segment, InstructionSet::x86::DataSize::None) << ':';
|
||||
|
||||
stream << '[';
|
||||
bool addOffset = false;
|
||||
switch(source) {
|
||||
default: break;
|
||||
@ -408,11 +417,11 @@ std::string InstructionSet::x86::to_string(
|
||||
addOffset = true;
|
||||
break;
|
||||
case Source::DirectAddress:
|
||||
stream << to_hex(instruction.offset(), 4);
|
||||
stream << std::uppercase << std::hex << instruction.offset() << 'h';
|
||||
break;
|
||||
}
|
||||
if(addOffset) {
|
||||
append(stream, instruction.offset(), offset_length, "+");
|
||||
append_signed(stream, instruction.offset(), offset_length);
|
||||
}
|
||||
stream << ']';
|
||||
return stream.str();
|
||||
@ -424,67 +433,96 @@ std::string InstructionSet::x86::to_string(
|
||||
|
||||
template<bool is_32bit>
|
||||
std::string InstructionSet::x86::to_string(
|
||||
Instruction<is_32bit> instruction,
|
||||
std::pair<int, Instruction<is_32bit>> instruction,
|
||||
Model model,
|
||||
int offset_length,
|
||||
int immediate_length
|
||||
) {
|
||||
std::string operation;
|
||||
|
||||
// Add segment override, if any, ahead of some operations that won't otherwise print it.
|
||||
switch(instruction.second.operation) {
|
||||
default: break;
|
||||
|
||||
case Operation::CMPS:
|
||||
case Operation::SCAS:
|
||||
case Operation::STOS:
|
||||
case Operation::LODS:
|
||||
case Operation::MOVS:
|
||||
switch(instruction.second.segment_override()) {
|
||||
default: break;
|
||||
case Source::ES: operation += "es "; break;
|
||||
case Source::CS: operation += "cs "; break;
|
||||
case Source::DS: operation += "ds "; break;
|
||||
case Source::SS: operation += "ss "; break;
|
||||
case Source::GS: operation += "gs "; break;
|
||||
case Source::FS: operation += "fs "; break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// Add a repetition prefix; it'll be one of 'rep', 'repe' or 'repne'.
|
||||
switch(instruction.repetition()) {
|
||||
switch(instruction.second.repetition()) {
|
||||
case Repetition::None: break;
|
||||
case Repetition::RepE:
|
||||
switch(instruction.operation) {
|
||||
default:
|
||||
switch(instruction.second.operation) {
|
||||
case Operation::CMPS:
|
||||
case Operation::SCAS:
|
||||
operation += "repe ";
|
||||
break;
|
||||
|
||||
case Operation::MOVS:
|
||||
case Operation::STOS:
|
||||
case Operation::LODS:
|
||||
default:
|
||||
operation += "rep ";
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case Repetition::RepNE:
|
||||
operation += "repne ";
|
||||
switch(instruction.second.operation) {
|
||||
case Operation::CMPS:
|
||||
case Operation::SCAS:
|
||||
operation += "repne ";
|
||||
break;
|
||||
|
||||
default:
|
||||
operation += "rep ";
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// Add operation itself.
|
||||
operation += to_string(instruction.operation, instruction.operation_size(), model);
|
||||
operation += to_string(instruction.second.operation, instruction.second.operation_size(), model);
|
||||
operation += " ";
|
||||
|
||||
// Deal with a few special cases up front.
|
||||
switch(instruction.operation) {
|
||||
switch(instruction.second.operation) {
|
||||
default: {
|
||||
const int operands = max_displayed_operands(instruction.operation);
|
||||
const bool displacement = has_displacement(instruction.operation);
|
||||
const bool print_first = operands > 1 && instruction.destination().source() != Source::None;
|
||||
const int operands = max_displayed_operands(instruction.second.operation);
|
||||
const bool displacement = has_displacement(instruction.second.operation);
|
||||
const bool print_first = operands > 1 && instruction.second.destination().source() != Source::None;
|
||||
if(print_first) {
|
||||
operation += to_string(instruction.destination(), instruction, offset_length, immediate_length);
|
||||
operation += to_string(instruction.second.destination(), instruction.second, offset_length, immediate_length);
|
||||
}
|
||||
if(operands > 0 && instruction.source().source() != Source::None) {
|
||||
if(operands > 0 && instruction.second.source().source() != Source::None) {
|
||||
if(print_first) operation += ", ";
|
||||
operation += to_string(instruction.source(), instruction, offset_length, immediate_length);
|
||||
operation += to_string(instruction.second.source(), instruction.second, offset_length, immediate_length);
|
||||
}
|
||||
if(displacement) {
|
||||
operation += to_hex(instruction.displacement(), offset_length);
|
||||
operation += to_hex(instruction.second.displacement() + instruction.first, offset_length);
|
||||
}
|
||||
} break;
|
||||
|
||||
case Operation::CALLfar:
|
||||
case Operation::JMPfar: {
|
||||
switch(instruction.destination().source()) {
|
||||
switch(instruction.second.destination().source()) {
|
||||
case Source::Immediate:
|
||||
operation += "far 0x";
|
||||
operation += to_hex(instruction.segment(), 4, false);
|
||||
operation += ":0x";
|
||||
operation += to_hex(instruction.offset(), 4, false);
|
||||
operation += to_hex(instruction.second.segment(), 4, false);
|
||||
operation += "h:";
|
||||
operation += to_hex(instruction.second.offset(), 4, false);
|
||||
operation += "h";
|
||||
break;
|
||||
default:
|
||||
operation += to_string(instruction.destination(), instruction, offset_length, immediate_length);
|
||||
operation += to_string(instruction.second.destination(), instruction.second, offset_length, immediate_length);
|
||||
break;
|
||||
}
|
||||
} break;
|
||||
@ -492,35 +530,35 @@ std::string InstructionSet::x86::to_string(
|
||||
case Operation::LDS:
|
||||
case Operation::LES: // The test set labels the pointer type as dword, which I guess is technically accurate.
|
||||
// A full 32 bits will be loaded from that address in 16-bit mode.
|
||||
operation += to_string(instruction.destination(), instruction, offset_length, immediate_length);
|
||||
operation += to_string(instruction.second.destination(), instruction.second, offset_length, immediate_length);
|
||||
operation += ", ";
|
||||
operation += to_string(instruction.source(), instruction, offset_length, immediate_length, InstructionSet::x86::DataSize::DWord);
|
||||
operation += to_string(instruction.second.source(), instruction.second, offset_length, immediate_length, InstructionSet::x86::DataSize::DWord);
|
||||
break;
|
||||
|
||||
case Operation::IN:
|
||||
operation += to_string(instruction.destination(), instruction, offset_length, immediate_length);
|
||||
operation += to_string(instruction.second.destination(), instruction.second, offset_length, immediate_length);
|
||||
operation += ", ";
|
||||
switch(instruction.source().source()) {
|
||||
switch(instruction.second.source().source()) {
|
||||
case Source::DirectAddress:
|
||||
operation += to_hex(instruction.offset(), 2, true);
|
||||
operation += to_hex(uint8_t(instruction.second.offset()));
|
||||
break;
|
||||
default:
|
||||
operation += to_string(instruction.source(), instruction, offset_length, immediate_length, InstructionSet::x86::DataSize::Word);
|
||||
operation += to_string(instruction.second.source(), instruction.second, offset_length, immediate_length, InstructionSet::x86::DataSize::Word);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case Operation::OUT:
|
||||
switch(instruction.destination().source()) {
|
||||
switch(instruction.second.destination().source()) {
|
||||
case Source::DirectAddress:
|
||||
operation += to_hex(instruction.offset(), 2, true);
|
||||
operation += to_hex(uint8_t(instruction.second.offset()));
|
||||
break;
|
||||
default:
|
||||
operation += to_string(instruction.destination(), instruction, offset_length, immediate_length, InstructionSet::x86::DataSize::Word);
|
||||
operation += to_string(instruction.second.destination(), instruction.second, offset_length, immediate_length, InstructionSet::x86::DataSize::Word);
|
||||
break;
|
||||
}
|
||||
operation += ", ";
|
||||
operation += to_string(instruction.source(), instruction, offset_length, immediate_length);
|
||||
operation += to_string(instruction.second.source(), instruction.second, offset_length, immediate_length);
|
||||
break;
|
||||
|
||||
// Rolls and shifts list eCX as a source on the understanding that everyone knows that rolls and shifts
|
||||
@ -530,18 +568,18 @@ std::string InstructionSet::x86::to_string(
|
||||
case Operation::SAL: case Operation::SAR:
|
||||
case Operation::SHR:
|
||||
case Operation::SETMO: case Operation::SETMOC:
|
||||
operation += to_string(instruction.destination(), instruction, offset_length, immediate_length);
|
||||
switch(instruction.source().source()) {
|
||||
operation += to_string(instruction.second.destination(), instruction.second, offset_length, immediate_length);
|
||||
switch(instruction.second.source().source()) {
|
||||
case Source::None: break;
|
||||
case Source::eCX: operation += ", cl"; break;
|
||||
case Source::Immediate:
|
||||
// Providing an immediate operand of 1 is a little future-proofing by the decoder; the '1'
|
||||
// is actually implicit on a real 8088. So omit it.
|
||||
if(instruction.operand() == 1) break;
|
||||
if(instruction.second.operand() == 1) break;
|
||||
[[fallthrough]];
|
||||
default:
|
||||
operation += ", ";
|
||||
operation += to_string(instruction.source(), instruction, offset_length, immediate_length);
|
||||
operation += to_string(instruction.second.source(), instruction.second, offset_length, immediate_length);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
@ -560,7 +598,7 @@ std::string InstructionSet::x86::to_string(
|
||||
//);
|
||||
|
||||
template std::string InstructionSet::x86::to_string(
|
||||
Instruction<false> instruction,
|
||||
std::pair<int, Instruction<false>> instruction,
|
||||
Model model,
|
||||
int offset_length,
|
||||
int immediate_length
|
||||
|
@ -69,11 +69,11 @@ enum class Operation: uint8_t {
|
||||
SBB,
|
||||
/// Subtract; source, destination, operand and displacement will be populated appropriately.
|
||||
SUB,
|
||||
/// Unsigned multiply; multiplies the source value by AX or AL, storing the result in DX:AX or AX.
|
||||
/// Unsigned multiply; multiplies the source value by EAX, AX or AL, storing the result in EDX:EAX, DX:AX or AX.
|
||||
MUL,
|
||||
/// Single operand signed multiply; multiplies the source value by AX or AL, storing the result in DX:AX or AX.
|
||||
/// Single operand signed multiply; multiplies the source value by EAX, AX or AL, storing the result in EDX:EAX, DX:AX or AX.
|
||||
IMUL_1,
|
||||
/// Unsigned divide; divide the source value by AX or AL, storing the quotient in AL and the remainder in AH.
|
||||
/// Unsigned divide; divide the AX, DX:AX or EDX:AX by the source(), storing the quotient in AL, AX or EAX and the remainder in AH, DX or EDX.
|
||||
DIV,
|
||||
/// Signed divide; divide the source value by AX or AL, storing the quotient in AL and the remainder in AH.
|
||||
IDIV,
|
||||
@ -144,9 +144,9 @@ enum class Operation: uint8_t {
|
||||
|
||||
/// Loads the destination with the source.
|
||||
MOV,
|
||||
/// Negatives; source and destination point to the same thing, to negative.
|
||||
/// Negatives; source indicates what to negative.
|
||||
NEG,
|
||||
/// Logical NOT; source and destination point to the same thing, to negative.
|
||||
/// Logical NOT; source indicates what to negative.
|
||||
NOT,
|
||||
/// Logical AND; source, destination, operand and displacement will be populated appropriately.
|
||||
AND,
|
||||
@ -195,7 +195,7 @@ enum class Operation: uint8_t {
|
||||
CLI,
|
||||
/// Set carry flag.
|
||||
STC,
|
||||
/// Set decimal flag.
|
||||
/// Set direction flag.
|
||||
STD,
|
||||
/// Set interrupt flag.
|
||||
STI,
|
||||
@ -370,6 +370,10 @@ enum class DataSize: uint8_t {
|
||||
None = 3,
|
||||
};
|
||||
|
||||
template <DataSize size> struct DataSizeType { using type = uint8_t; };
|
||||
template <> struct DataSizeType<DataSize::Word> { using type = uint16_t; };
|
||||
template <> struct DataSizeType<DataSize::DWord> { using type = uint32_t; };
|
||||
|
||||
constexpr int byte_size(DataSize size) {
|
||||
return (1 << int(size)) & 7;
|
||||
}
|
||||
@ -383,6 +387,9 @@ enum class AddressSize: uint8_t {
|
||||
b32 = 1,
|
||||
};
|
||||
|
||||
template <AddressSize size> struct AddressSizeType { using type = uint16_t; };
|
||||
template <> struct AddressSizeType<AddressSize::b32> { using type = uint32_t; };
|
||||
|
||||
constexpr DataSize data_size(AddressSize size) {
|
||||
return DataSize(int(size) + 1);
|
||||
}
|
||||
@ -458,16 +465,14 @@ enum class Repetition: uint8_t {
|
||||
};
|
||||
|
||||
/// @returns @c true if @c operation supports repetition mode @c repetition; @c false otherwise.
|
||||
constexpr bool supports(Operation operation, Repetition repetition) {
|
||||
constexpr bool supports(Operation operation, [[maybe_unused]] Repetition repetition) {
|
||||
switch(operation) {
|
||||
default: return false;
|
||||
|
||||
case Operation::INS:
|
||||
case Operation::OUTS:
|
||||
return repetition == Repetition::RepE;
|
||||
|
||||
case Operation::Invalid: // Retain context here; it's used as an intermediate
|
||||
// state sometimes.
|
||||
case Operation::INS:
|
||||
case Operation::OUTS:
|
||||
case Operation::CMPS:
|
||||
case Operation::LODS:
|
||||
case Operation::MOVS:
|
||||
@ -475,8 +480,12 @@ constexpr bool supports(Operation operation, Repetition repetition) {
|
||||
case Operation::STOS:
|
||||
return true;
|
||||
|
||||
case Operation::IDIV:
|
||||
return repetition == Repetition::RepNE;
|
||||
// TODO: my new understanding is that the 8086 and 8088 recognise rep and repne on
|
||||
// IDIV — and possibly DIV — as a quirk, affecting the outcome (possibly negativing the result?).
|
||||
// So the test below should be a function of model, if I come to a conclusion about whether I'm
|
||||
// going for fidelity to the instruction set as generally implemented, or to Intel's specific implementation.
|
||||
// case Operation::IDIV:
|
||||
// return repetition == Repetition::RepNE;
|
||||
}
|
||||
}
|
||||
|
||||
@ -589,10 +598,7 @@ class DataPointer {
|
||||
);
|
||||
}
|
||||
|
||||
template <bool obscure_indirectNoBase = false> constexpr Source source() const {
|
||||
if constexpr (obscure_indirectNoBase) {
|
||||
return (source_ >= Source::IndirectNoBase) ? Source::Indirect : source_;
|
||||
}
|
||||
constexpr Source source() const {
|
||||
return source_;
|
||||
}
|
||||
|
||||
@ -621,10 +627,14 @@ class DataPointer {
|
||||
}
|
||||
}
|
||||
|
||||
template <bool obscure_indirectNoBase = false> constexpr Source base() const {
|
||||
if constexpr (obscure_indirectNoBase) {
|
||||
return (source_ <= Source::IndirectNoBase) ? Source::None : sib_.base();
|
||||
}
|
||||
constexpr Source segment(Source segment_override) const {
|
||||
// TODO: remove conditionality here.
|
||||
if(segment_override != Source::None) return segment_override;
|
||||
if(const auto segment = default_segment(); segment != Source::None) return segment;
|
||||
return Source::DS;
|
||||
}
|
||||
|
||||
constexpr Source base() const {
|
||||
return sib_.base();
|
||||
}
|
||||
|
||||
@ -689,6 +699,16 @@ template<bool is_32bit> class Instruction {
|
||||
// [b4, b0]: dest.
|
||||
uint16_t source_data_dest_sib_ = 1 << 10; // So that ::Invalid doesn't seem to have a length extension.
|
||||
|
||||
// Note to future self: if source length continues to prove avoidable, reuse its four bits as:
|
||||
// three bits: segment (as overridden, otherwise whichever operand has a segment, if either);
|
||||
// one bit: an extra bit for Operation.
|
||||
//
|
||||
// Then what was the length extension will hold only a repetition, if any, and the lock bit. As luck would have
|
||||
// it there are six valid segment registers so there is an available sentinel value to put into the segment
|
||||
// field to indicate that there's an extension if necessary. A further three bits would need to be trimmed
|
||||
// to do away with that extension entirely, but since lock is rarely used and repetitions apply only to a
|
||||
// small number of operations I think it'd at least be a limited problem.
|
||||
|
||||
bool has_length_extension() const {
|
||||
return !((source_data_dest_sib_ >> 10) & 15);
|
||||
}
|
||||
@ -762,7 +782,7 @@ template<bool is_32bit> class Instruction {
|
||||
/// On x86 a segment override cannot modify the segment used as a destination in string instructions,
|
||||
/// or that used by stack instructions, but this function does not spend the time necessary to provide
|
||||
/// the correct default for those.
|
||||
Source data_segment() const {
|
||||
Source segment_override() const {
|
||||
if(!has_length_extension()) return Source::None;
|
||||
return Source(
|
||||
int(Source::ES) +
|
||||
@ -774,15 +794,19 @@ template<bool is_32bit> class Instruction {
|
||||
if(!has_length_extension()) return Repetition::None;
|
||||
return Repetition((length_extension() >> 4) & 3);
|
||||
}
|
||||
|
||||
/// @returns The data size of this operation — e.g. `MOV AX, BX` has a data size of `::Word` but `MOV EAX, EBX` has a data size of
|
||||
/// `::DWord`. This value is guaranteed never to be `DataSize::None` even for operations such as `CLI` that don't have operands and operate
|
||||
/// on data that is not a byte, word or double word.
|
||||
DataSize operation_size() const {
|
||||
return DataSize(source_data_dest_sib_ >> 14);
|
||||
}
|
||||
|
||||
int length() const {
|
||||
const int short_length = (source_data_dest_sib_ >> 10) & 15;
|
||||
if(short_length) return short_length;
|
||||
return length_extension() >> 6;
|
||||
}
|
||||
// int length() const {
|
||||
// const int short_length = (source_data_dest_sib_ >> 10) & 15;
|
||||
// if(short_length) return short_length;
|
||||
// return length_extension() >> 6;
|
||||
// }
|
||||
|
||||
ImmediateT operand() const {
|
||||
const ImmediateT ops[] = {0, operand_extension()};
|
||||
@ -902,7 +926,7 @@ 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.
|
||||
template<bool is_32bit>
|
||||
std::string to_string(
|
||||
Instruction<is_32bit> instruction,
|
||||
std::pair<int, Instruction<is_32bit>> instruction,
|
||||
Model model,
|
||||
int offset_length = 0,
|
||||
int immediate_length = 0);
|
||||
|
24
InstructionSets/x86/Interrupts.hpp
Normal file
24
InstructionSets/x86/Interrupts.hpp
Normal file
@ -0,0 +1,24 @@
|
||||
//
|
||||
// Interrupts.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 06/10/2023.
|
||||
// Copyright © 2023 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef InstructionSets_x86_Interrupts_h
|
||||
#define InstructionSets_x86_Interrupts_h
|
||||
|
||||
namespace InstructionSet::x86 {
|
||||
|
||||
enum Interrupt {
|
||||
DivideError = 0,
|
||||
SingleStep = 1,
|
||||
NMI = 2,
|
||||
OneByte = 3,
|
||||
OnOverflow = 4,
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif /* InstructionSets_x86_Interrupts_h */
|
@ -9,6 +9,8 @@
|
||||
#ifndef Model_h
|
||||
#define Model_h
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace InstructionSet::x86 {
|
||||
|
||||
enum class Model {
|
||||
@ -20,6 +22,9 @@ enum class Model {
|
||||
|
||||
static constexpr bool is_32bit(Model model) { return model >= Model::i80386; }
|
||||
|
||||
template <bool is_32bit> struct AddressT { using type = uint16_t; };
|
||||
template <> struct AddressT<true> { using type = uint32_t; };
|
||||
|
||||
}
|
||||
|
||||
#endif /* Model_h */
|
||||
|
42
InstructionSets/x86/Perform.hpp
Normal file
42
InstructionSets/x86/Perform.hpp
Normal file
@ -0,0 +1,42 @@
|
||||
//
|
||||
// Perform.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 05/10/2023.
|
||||
// Copyright © 2023 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef Perform_h
|
||||
#define Perform_h
|
||||
|
||||
#include "Instruction.hpp"
|
||||
#include "Model.hpp"
|
||||
#include "Status.hpp"
|
||||
|
||||
namespace InstructionSet::x86 {
|
||||
|
||||
/// 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.
|
||||
///
|
||||
/// Any change in processor status will be applied to @c status.
|
||||
template <
|
||||
Model model,
|
||||
typename InstructionT,
|
||||
typename FlowControllerT,
|
||||
typename RegistersT,
|
||||
typename MemoryT,
|
||||
typename IOT
|
||||
> void perform(
|
||||
const InstructionT &instruction,
|
||||
Status &status,
|
||||
FlowControllerT &flow_controller,
|
||||
RegistersT ®isters,
|
||||
MemoryT &memory,
|
||||
IOT &io
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
#include "Implementation/PerformImplementation.hpp"
|
||||
|
||||
#endif /* Perform_h */
|
233
InstructionSets/x86/Status.hpp
Normal file
233
InstructionSets/x86/Status.hpp
Normal file
@ -0,0 +1,233 @@
|
||||
//
|
||||
// Status.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 05/10/2023.
|
||||
// Copyright © 2023 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef InstructionSets_x86_Status_hpp
|
||||
#define InstructionSets_x86_Status_hpp
|
||||
|
||||
#include "../../Numeric/Carry.hpp"
|
||||
|
||||
namespace InstructionSet::x86 {
|
||||
|
||||
namespace ConditionCode {
|
||||
|
||||
//
|
||||
// Standard flags.
|
||||
//
|
||||
|
||||
static constexpr uint32_t Carry = 1 << 0;
|
||||
static constexpr uint32_t Parity = 1 << 2;
|
||||
static constexpr uint32_t AuxiliaryCarry = 1 << 4;
|
||||
static constexpr uint32_t Zero = 1 << 6;
|
||||
static constexpr uint32_t Sign = 1 << 7;
|
||||
static constexpr uint32_t Trap = 1 << 8;
|
||||
static constexpr uint32_t Interrupt = 1 << 9;
|
||||
static constexpr uint32_t Direction = 1 << 10;
|
||||
static constexpr uint32_t Overflow = 1 << 11;
|
||||
|
||||
//
|
||||
// 80286+ additions.
|
||||
//
|
||||
|
||||
static constexpr uint32_t IOPrivilege = (1 << 12) | (1 << 13);
|
||||
static constexpr uint32_t NestedTask = 1 << 14;
|
||||
|
||||
//
|
||||
// 16-bit protected mode flags.
|
||||
//
|
||||
|
||||
static constexpr uint32_t ProtectionEnable = 1 << 16;
|
||||
static constexpr uint32_t MonitorProcessorExtension = 1 << 17;
|
||||
static constexpr uint32_t ProcessorExtensionExtension = 1 << 18;
|
||||
static constexpr uint32_t TaskSwitch = 1 << 19;
|
||||
|
||||
//
|
||||
// 32-bit protected mode flags.
|
||||
//
|
||||
|
||||
static constexpr uint32_t Resume = 1 << 16;
|
||||
static constexpr uint32_t VirtualMode = 1 << 17;
|
||||
|
||||
}
|
||||
|
||||
enum class Flag {
|
||||
Carry,
|
||||
AuxiliaryCarry,
|
||||
Sign,
|
||||
Overflow,
|
||||
Trap,
|
||||
Interrupt,
|
||||
Direction,
|
||||
Zero,
|
||||
ParityOdd
|
||||
};
|
||||
|
||||
enum class Condition {
|
||||
Overflow,
|
||||
Below,
|
||||
Zero,
|
||||
BelowOrEqual,
|
||||
Sign,
|
||||
ParityOdd,
|
||||
Less,
|
||||
LessOrEqual
|
||||
};
|
||||
|
||||
class Status {
|
||||
public:
|
||||
using FlagT = uint32_t;
|
||||
|
||||
// Flag getters.
|
||||
template <Flag flag> bool flag() const {
|
||||
switch(flag) {
|
||||
case Flag::Carry: return carry_;
|
||||
case Flag::AuxiliaryCarry: return auxiliary_carry_;
|
||||
case Flag::Sign: return sign_;
|
||||
case Flag::Overflow: return overflow_;
|
||||
case Flag::Trap: return trap_;
|
||||
case Flag::Interrupt: return interrupt_;
|
||||
case Flag::Direction: return direction_ < 0;
|
||||
case Flag::Zero: return !zero_;
|
||||
case Flag::ParityOdd: return not_parity_bit();
|
||||
}
|
||||
}
|
||||
|
||||
// Condition evaluation.
|
||||
template <Condition test> bool condition() const {
|
||||
switch(test) {
|
||||
case Condition::Overflow: return flag<Flag::Overflow>();
|
||||
case Condition::Below: return flag<Flag::Carry>();
|
||||
case Condition::Zero: return flag<Flag::Zero>();
|
||||
case Condition::BelowOrEqual: return flag<Flag::Zero>() || flag<Flag::Carry>();
|
||||
case Condition::Sign: return flag<Flag::Sign>();
|
||||
case Condition::ParityOdd: return flag<Flag::ParityOdd>();
|
||||
case Condition::Less: return flag<Flag::Sign>() != flag<Flag::Overflow>();
|
||||
case Condition::LessOrEqual: return flag<Flag::Zero>() || flag<Flag::Sign>() != flag<Flag::Overflow>();
|
||||
}
|
||||
}
|
||||
|
||||
// Convenience setters.
|
||||
|
||||
/// Sets all of @c flags as a function of @c value:
|
||||
/// • Flag::Zero: sets the zero flag if @c value is zero;
|
||||
/// • Flag::Sign: sets the sign flag if the top bit of @c value is one;
|
||||
/// • Flag::ParityOdd: sets parity based on the low 8 bits of @c value;
|
||||
/// • Flag::Carry: sets carry if @c value is non-zero;
|
||||
/// • Flag::AuxiliaryCarry: sets auxiliary carry if @c value is non-zero;
|
||||
/// • Flag::Overflow: sets overflow if @c value is non-zero;
|
||||
/// • Flag::Interrupt: sets interrupt if @c value is non-zero;
|
||||
/// • Flag::Trap: sets interrupt if @c value is non-zero;
|
||||
/// • Flag::Direction: sets direction if @c value is non-zero.
|
||||
template <typename IntT, Flag... flags> void set_from(IntT value) {
|
||||
for(const auto flag: {flags...}) {
|
||||
switch(flag) {
|
||||
default: break;
|
||||
case Flag::Zero: zero_ = value; break;
|
||||
case Flag::Sign: sign_ = value & Numeric::top_bit<IntT>(); break;
|
||||
case Flag::ParityOdd: parity_ = value; break;
|
||||
case Flag::Carry: carry_ = value; break;
|
||||
case Flag::AuxiliaryCarry: auxiliary_carry_ = value; break;
|
||||
case Flag::Overflow: overflow_ = value; break;
|
||||
case Flag::Interrupt: interrupt_ = value; break;
|
||||
case Flag::Trap: trap_ = value; break;
|
||||
case Flag::Direction: direction_ = value ? -1 : 1; break;
|
||||
}
|
||||
}
|
||||
}
|
||||
template <Flag... flags> void set_from(FlagT value) {
|
||||
set_from<FlagT, flags...>(value);
|
||||
}
|
||||
|
||||
template <typename IntT> IntT carry_bit() const { return carry_ ? 1 : 0; }
|
||||
bool not_parity_bit() const {
|
||||
// x86 parity always considers the lowest 8-bits only.
|
||||
auto result = static_cast<uint8_t>(parity_);
|
||||
result ^= result >> 4;
|
||||
result ^= result >> 2;
|
||||
result ^= result >> 1;
|
||||
return result & 1;
|
||||
}
|
||||
|
||||
template <typename IntT> IntT direction() const { return static_cast<IntT>(direction_); }
|
||||
|
||||
// Complete value get and set.
|
||||
void set(uint16_t value) {
|
||||
set_from<Flag::Carry>(value & ConditionCode::Carry);
|
||||
set_from<Flag::AuxiliaryCarry>(value & ConditionCode::AuxiliaryCarry);
|
||||
set_from<Flag::Overflow>(value & ConditionCode::Overflow);
|
||||
set_from<Flag::Trap>(value & ConditionCode::Trap);
|
||||
set_from<Flag::Interrupt>(value & ConditionCode::Interrupt);
|
||||
set_from<Flag::Direction>(value & ConditionCode::Direction);
|
||||
|
||||
set_from<uint8_t, Flag::Sign>(value);
|
||||
|
||||
set_from<Flag::Zero>((~value) & ConditionCode::Zero);
|
||||
set_from<Flag::ParityOdd>((~value) & ConditionCode::Parity);
|
||||
}
|
||||
|
||||
uint16_t get() const {
|
||||
return
|
||||
0xf002 |
|
||||
|
||||
(flag<Flag::Carry>() ? ConditionCode::Carry : 0) |
|
||||
(flag<Flag::AuxiliaryCarry>() ? ConditionCode::AuxiliaryCarry : 0) |
|
||||
(flag<Flag::Sign>() ? ConditionCode::Sign : 0) |
|
||||
(flag<Flag::Overflow>() ? ConditionCode::Overflow : 0) |
|
||||
(flag<Flag::Trap>() ? ConditionCode::Trap : 0) |
|
||||
(flag<Flag::Interrupt>() ? ConditionCode::Interrupt : 0) |
|
||||
(flag<Flag::Direction>() ? ConditionCode::Direction : 0) |
|
||||
(flag<Flag::Zero>() ? ConditionCode::Zero : 0) |
|
||||
|
||||
(flag<Flag::ParityOdd>() ? 0 : ConditionCode::Parity);
|
||||
}
|
||||
|
||||
std::string to_string() const {
|
||||
std::string result;
|
||||
|
||||
if(flag<Flag::Overflow>()) result += "O"; else result += "-";
|
||||
if(flag<Flag::Direction>()) result += "D"; else result += "-";
|
||||
if(flag<Flag::Interrupt>()) result += "I"; else result += "-";
|
||||
if(flag<Flag::Trap>()) result += "T"; else result += "-";
|
||||
if(flag<Flag::Sign>()) result += "S"; else result += "-";
|
||||
if(flag<Flag::Zero>()) result += "Z"; else result += "-";
|
||||
result += "-";
|
||||
if(flag<Flag::AuxiliaryCarry>()) result += "A"; else result += "-";
|
||||
result += "-";
|
||||
if(!flag<Flag::ParityOdd>()) result += "P"; else result += "-";
|
||||
result += "-";
|
||||
if(flag<Flag::Carry>()) result += "C"; else result += "-";
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool operator ==(const Status &rhs) const {
|
||||
return get() == rhs.get();
|
||||
}
|
||||
|
||||
private:
|
||||
// Non-zero => set; zero => unset.
|
||||
uint32_t carry_;
|
||||
uint32_t auxiliary_carry_;
|
||||
uint32_t sign_;
|
||||
uint32_t overflow_;
|
||||
uint32_t trap_;
|
||||
uint32_t interrupt_;
|
||||
|
||||
// +1 = direction flag not set;
|
||||
// -1 = direction flag set.
|
||||
int32_t direction_;
|
||||
|
||||
// Zero => set; non-zero => unset.
|
||||
uint32_t zero_;
|
||||
|
||||
// Odd number of bits => set; even => unset.
|
||||
uint32_t parity_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif /* InstructionSets_x86_Status_hpp */
|
@ -9,21 +9,73 @@
|
||||
#ifndef Carry_hpp
|
||||
#define Carry_hpp
|
||||
|
||||
#include <limits>
|
||||
|
||||
namespace Numeric {
|
||||
|
||||
/// @returns @c true if there was carry out of @c bit when @c source1 and @c source2 were added, producing @c result.
|
||||
template <int bit, typename IntT> bool carried_out(IntT source1, IntT source2, IntT result) {
|
||||
/// @returns @c true if from @c bit there was:
|
||||
/// • carry after calculating @c lhs + @c rhs if @c is_add is true; or
|
||||
/// • borrow after calculating @c lhs - @c rhs if @c is_add is false;
|
||||
/// producing @c result.
|
||||
template <bool is_add, int bit, typename IntT> bool carried_out(IntT lhs, IntT rhs, IntT result) {
|
||||
// Additive:
|
||||
//
|
||||
// 0 and 0 => didn't.
|
||||
// 0 and 1 or 1 and 0 => did if 0.
|
||||
// 1 and 1 => did.
|
||||
return IntT(1 << bit) & (source1 | source2) & ((source1 & source2) | ~result);
|
||||
//
|
||||
// Subtractive:
|
||||
//
|
||||
// 1 and 0 => didn't
|
||||
// 1 and 1 or 0 and 0 => did if 1.
|
||||
// 0 and 1 => did.
|
||||
if constexpr (!is_add) {
|
||||
rhs = ~rhs;
|
||||
}
|
||||
const bool carry = IntT(1 << bit) & (lhs | rhs) & ((lhs & rhs) | ~result);
|
||||
if constexpr (!is_add) {
|
||||
return !carry;
|
||||
} else {
|
||||
return carry;
|
||||
}
|
||||
}
|
||||
|
||||
/// @returns @c true if there was carry into @c bit when @c source1 and @c source2 were added, producing @c result.
|
||||
template <int bit, typename IntT> bool carried_in(IntT source1, IntT source2, IntT result) {
|
||||
// 0 and 0 or 1 and 1 => did if 1
|
||||
// 0 and 1 or 1 and 0 => did if 0
|
||||
return IntT(1 << bit) & (source1 ^ source2 ^ result);
|
||||
/// @returns @c true if there was carry into @c bit when computing either:
|
||||
/// • @c lhs + @c rhs; or
|
||||
/// • @c lhs - @c rhs;
|
||||
/// producing @c result.
|
||||
template <int bit, typename IntT> bool carried_in(IntT lhs, IntT rhs, IntT result) {
|
||||
// 0 and 0 or 1 and 1 => did if 1.
|
||||
// 0 and 1 or 1 and 0 => did if 0.
|
||||
return IntT(1 << bit) & (lhs ^ rhs ^ result);
|
||||
}
|
||||
|
||||
/// @returns An int of type @c IntT with only the most-significant bit set.
|
||||
template <typename IntT> constexpr IntT top_bit() {
|
||||
static_assert(!std::numeric_limits<IntT>::is_signed);
|
||||
constexpr IntT max = std::numeric_limits<IntT>::max();
|
||||
return max - (max >> 1);
|
||||
}
|
||||
|
||||
/// @returns The number of bits in @c IntT.
|
||||
template <typename IntT> constexpr int bit_size() {
|
||||
return sizeof(IntT) * 8;
|
||||
}
|
||||
|
||||
/// @returns An int with the top bit indicating whether overflow occurred during the calculation of
|
||||
/// • @c lhs + @c rhs (if @c is_add is true); or
|
||||
/// • @c lhs - @c rhs (if @c is_add is false)
|
||||
/// and the result was @c result. All other bits will be clear.
|
||||
template <bool is_add, typename IntT>
|
||||
IntT overflow(IntT lhs, IntT rhs, IntT result) {
|
||||
const IntT output_changed = result ^ lhs;
|
||||
const IntT input_differed = lhs ^ rhs;
|
||||
|
||||
if constexpr (is_add) {
|
||||
return top_bit<IntT>() & output_changed & ~input_differed;
|
||||
} else {
|
||||
return top_bit<IntT>() & output_changed & input_differed;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1048,7 +1048,6 @@
|
||||
4BE21219253FCE9C00435408 /* AppleIIgs.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BE21214253FCE9C00435408 /* AppleIIgs.cpp */; };
|
||||
4BE2121A253FCE9C00435408 /* AppleIIgs.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BE21214253FCE9C00435408 /* AppleIIgs.cpp */; };
|
||||
4BE34438238389E10058E78F /* AtariSTVideoTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BE34437238389E10058E78F /* AtariSTVideoTests.mm */; };
|
||||
4BE3C69727CC32DC000EAD28 /* x86DataPointerTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BE3C69627CC32DC000EAD28 /* x86DataPointerTests.mm */; };
|
||||
4BE76CF922641ED400ACD6FA /* QLTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BE76CF822641ED300ACD6FA /* QLTests.mm */; };
|
||||
4BE8EB6625C750B50040BC40 /* DAT.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BE8EB6425C750B50040BC40 /* DAT.cpp */; };
|
||||
4BE90FFD22D5864800FB464D /* MacintoshVideoTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BE90FFC22D5864800FB464D /* MacintoshVideoTests.mm */; };
|
||||
@ -1126,6 +1125,10 @@
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
423BDC492AB24699008E37B6 /* 8088Tests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = 8088Tests.mm; sourceTree = "<group>"; };
|
||||
42437B342ACF02A9006DFED1 /* Status.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Status.hpp; sourceTree = "<group>"; };
|
||||
42437B352ACF0AA2006DFED1 /* Perform.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Perform.hpp; sourceTree = "<group>"; };
|
||||
42437B382ACF2798006DFED1 /* PerformImplementation.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = PerformImplementation.hpp; sourceTree = "<group>"; };
|
||||
42437B392AD07465006DFED1 /* Interrupts.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Interrupts.hpp; sourceTree = "<group>"; };
|
||||
4281572E2AA0334300E16AA1 /* Carry.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Carry.hpp; sourceTree = "<group>"; };
|
||||
428168372A16C25C008ECD27 /* LineLayout.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = LineLayout.hpp; sourceTree = "<group>"; };
|
||||
428168392A37AFB4008ECD27 /* DispatcherTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = DispatcherTests.mm; sourceTree = "<group>"; };
|
||||
@ -2210,9 +2213,7 @@
|
||||
4BE3231520532AA7006EF799 /* Target.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Target.hpp; sourceTree = "<group>"; };
|
||||
4BE3231620532BED006EF799 /* Target.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Target.hpp; sourceTree = "<group>"; };
|
||||
4BE34437238389E10058E78F /* AtariSTVideoTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = AtariSTVideoTests.mm; sourceTree = "<group>"; };
|
||||
4BE3C69327C793EF000EAD28 /* DataPointerResolver.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = DataPointerResolver.hpp; sourceTree = "<group>"; };
|
||||
4BE3C69527CBC540000EAD28 /* Model.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Model.hpp; sourceTree = "<group>"; };
|
||||
4BE3C69627CC32DC000EAD28 /* x86DataPointerTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = x86DataPointerTests.mm; sourceTree = "<group>"; };
|
||||
4BE76CF822641ED300ACD6FA /* QLTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = QLTests.mm; sourceTree = "<group>"; };
|
||||
4BE845201F2FF7F100A5EA22 /* CRTC6845.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CRTC6845.hpp; sourceTree = "<group>"; };
|
||||
4BE8EB5425C0E9D40040BC40 /* Disassembler.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Disassembler.hpp; sourceTree = "<group>"; };
|
||||
@ -2323,6 +2324,14 @@
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
42437B372ACF2798006DFED1 /* Implementation */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
42437B382ACF2798006DFED1 /* PerformImplementation.hpp */,
|
||||
);
|
||||
path = Implementation;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
42A5E8322ABBE16F00A0DD5D /* Neskell Tests */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@ -4381,7 +4390,6 @@
|
||||
4B8DD3672633B2D400B3C866 /* SpectrumVideoContentionTests.mm */,
|
||||
4B2AF8681E513FC20027EE29 /* TIATests.mm */,
|
||||
4B1D08051E0F7A1100763741 /* TimeTests.mm */,
|
||||
4BE3C69627CC32DC000EAD28 /* x86DataPointerTests.mm */,
|
||||
4BEE4BD325A26E2B00011BD2 /* x86DecoderTests.mm */,
|
||||
4BDA8234261E8E000021AA19 /* Z80ContentionTests.mm */,
|
||||
4BB73EB81B587A5100552FC2 /* Info.plist */,
|
||||
@ -4989,10 +4997,13 @@
|
||||
children = (
|
||||
4BEDA3B925B25563000C2DBD /* Decoder.cpp */,
|
||||
4B69DEB52AB79E4F0055B217 /* Instruction.cpp */,
|
||||
4BE3C69327C793EF000EAD28 /* DataPointerResolver.hpp */,
|
||||
4BEDA3B825B25563000C2DBD /* Decoder.hpp */,
|
||||
4BEDA3DB25B2588F000C2DBD /* Instruction.hpp */,
|
||||
4BE3C69527CBC540000EAD28 /* Model.hpp */,
|
||||
42437B352ACF0AA2006DFED1 /* Perform.hpp */,
|
||||
42437B342ACF02A9006DFED1 /* Status.hpp */,
|
||||
42437B372ACF2798006DFED1 /* Implementation */,
|
||||
42437B392AD07465006DFED1 /* Interrupts.hpp */,
|
||||
);
|
||||
path = x86;
|
||||
sourceTree = "<group>";
|
||||
@ -6235,7 +6246,6 @@
|
||||
4B7752AA28217E370073E2C5 /* ROMCatalogue.cpp in Sources */,
|
||||
4B778F0323A5EBB00000D260 /* FAT12.cpp in Sources */,
|
||||
4B778F4023A5F1910000D260 /* z8530.cpp in Sources */,
|
||||
4BE3C69727CC32DC000EAD28 /* x86DataPointerTests.mm in Sources */,
|
||||
4B778EFD23A5EB8E0000D260 /* AppleDSK.cpp in Sources */,
|
||||
4B7752B728217EF40073E2C5 /* Chipset.cpp in Sources */,
|
||||
4B778EFB23A5EB7E0000D260 /* HFE.cpp in Sources */,
|
||||
|
@ -18,6 +18,8 @@
|
||||
#include "NSData+dataWithContentsOfGZippedFile.h"
|
||||
|
||||
#include "../../../InstructionSets/x86/Decoder.hpp"
|
||||
#include "../../../InstructionSets/x86/Perform.hpp"
|
||||
#include "../../../Numeric/RegisterSizes.hpp"
|
||||
|
||||
namespace {
|
||||
|
||||
@ -25,16 +27,302 @@ namespace {
|
||||
// provide their real path here.
|
||||
constexpr char TestSuiteHome[] = "/Users/tharte/Projects/ProcessorTests/8088/v1";
|
||||
|
||||
using Status = InstructionSet::x86::Status;
|
||||
struct Registers {
|
||||
CPU::RegisterPair16 ax_;
|
||||
uint8_t &al() { return ax_.halves.low; }
|
||||
uint8_t &ah() { return ax_.halves.high; }
|
||||
uint16_t &ax() { return ax_.full; }
|
||||
|
||||
CPU::RegisterPair16 &axp() { return ax_; }
|
||||
|
||||
CPU::RegisterPair16 cx_;
|
||||
uint8_t &cl() { return cx_.halves.low; }
|
||||
uint8_t &ch() { return cx_.halves.high; }
|
||||
uint16_t &cx() { return cx_.full; }
|
||||
|
||||
CPU::RegisterPair16 dx_;
|
||||
uint8_t &dl() { return dx_.halves.low; }
|
||||
uint8_t &dh() { return dx_.halves.high; }
|
||||
uint16_t &dx() { return dx_.full; }
|
||||
|
||||
CPU::RegisterPair16 bx_;
|
||||
uint8_t &bl() { return bx_.halves.low; }
|
||||
uint8_t &bh() { return bx_.halves.high; }
|
||||
uint16_t &bx() { return bx_.full; }
|
||||
|
||||
uint16_t sp_;
|
||||
uint16_t &sp() { return sp_; }
|
||||
|
||||
uint16_t bp_;
|
||||
uint16_t &bp() { return bp_; }
|
||||
|
||||
uint16_t si_;
|
||||
uint16_t &si() { return si_; }
|
||||
|
||||
uint16_t di_;
|
||||
uint16_t &di() { return di_; }
|
||||
|
||||
uint16_t es_, cs_, ds_, ss_;
|
||||
|
||||
uint16_t ip_;
|
||||
uint16_t &ip() { return ip_; }
|
||||
|
||||
uint16_t &es() { return es_; }
|
||||
uint16_t &cs() { return cs_; }
|
||||
uint16_t &ds() { return ds_; }
|
||||
uint16_t &ss() { return ss_; }
|
||||
|
||||
bool operator ==(const Registers &rhs) const {
|
||||
return
|
||||
ax_.full == rhs.ax_.full &&
|
||||
cx_.full == rhs.cx_.full &&
|
||||
dx_.full == rhs.dx_.full &&
|
||||
bx_.full == rhs.bx_.full &&
|
||||
sp_ == rhs.sp_ &&
|
||||
bp_ == rhs.bp_ &&
|
||||
si_ == rhs.si_ &&
|
||||
di_ == rhs.di_ &&
|
||||
es_ == rhs.es_ &&
|
||||
cs_ == rhs.cs_ &&
|
||||
ds_ == rhs.ds_ &&
|
||||
si_ == rhs.si_ &&
|
||||
ip_ == rhs.ip_;
|
||||
}
|
||||
};
|
||||
struct Memory {
|
||||
enum class Tag {
|
||||
Seeded,
|
||||
AccessExpected,
|
||||
Accessed,
|
||||
FlagsL,
|
||||
FlagsH
|
||||
};
|
||||
|
||||
std::unordered_map<uint32_t, Tag> tags;
|
||||
std::vector<uint8_t> memory;
|
||||
const Registers ®isters_;
|
||||
|
||||
Memory(Registers ®isters) : registers_(registers) {
|
||||
memory.resize(1024*1024);
|
||||
}
|
||||
|
||||
void clear() {
|
||||
tags.clear();
|
||||
}
|
||||
|
||||
void seed(uint32_t address, uint8_t value) {
|
||||
memory[address] = value;
|
||||
tags[address] = Tag::Seeded;
|
||||
}
|
||||
|
||||
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,
|
||||
// so that defined-flag-only masks can be applied while verifying RAM contents.
|
||||
template <typename IntT> IntT &access(InstructionSet::x86::Source segment, uint16_t address, Tag tag) {
|
||||
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
|
||||
// to a selector, they're just at an absolute location.
|
||||
template <typename IntT> IntT &access(uint32_t address, Tag tag) {
|
||||
// Check for address wraparound
|
||||
if(address >= 0x10'0001 - sizeof(IntT)) {
|
||||
if constexpr (std::is_same_v<IntT, uint8_t>) {
|
||||
address &= 0xf'ffff;
|
||||
} else {
|
||||
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_;
|
||||
} else {
|
||||
address &= 0xf'ffff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(tags.find(address) == tags.end()) {
|
||||
printf("Access to unexpected RAM address");
|
||||
}
|
||||
tags[address] = tag;
|
||||
return *reinterpret_cast<IntT *>(&memory[address]);
|
||||
}
|
||||
|
||||
// Entry point for the 8086; simply notes that memory was accessed.
|
||||
template <typename IntT> IntT &access([[maybe_unused]] 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);
|
||||
return write_back_value_;
|
||||
}
|
||||
}
|
||||
return access<IntT>(segment, 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
template <typename IntT> void out([[maybe_unused]] uint16_t port, [[maybe_unused]] IntT value) {}
|
||||
template <typename IntT> IntT in([[maybe_unused]] uint16_t port) { return IntT(~0); }
|
||||
};
|
||||
class FlowController {
|
||||
public:
|
||||
FlowController(Memory &memory, Registers ®isters, Status &status) :
|
||||
memory_(memory), registers_(registers), status_(status) {}
|
||||
|
||||
void did_iret() {}
|
||||
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) {
|
||||
registers_.ip_ = address;
|
||||
}
|
||||
|
||||
void jump(uint16_t segment, uint16_t address) {
|
||||
registers_.cs_ = segment;
|
||||
registers_.ip_ = address;
|
||||
}
|
||||
|
||||
void halt() {}
|
||||
void wait() {}
|
||||
|
||||
void begin_instruction() {
|
||||
should_repeat_ = false;
|
||||
}
|
||||
void repeat_last() {
|
||||
should_repeat_ = true;
|
||||
}
|
||||
bool should_repeat() const {
|
||||
return should_repeat_;
|
||||
}
|
||||
|
||||
private:
|
||||
Memory &memory_;
|
||||
Registers ®isters_;
|
||||
Status &status_;
|
||||
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 {
|
||||
InstructionSet::x86::Status status;
|
||||
Registers registers;
|
||||
Memory memory;
|
||||
FlowController flow_controller;
|
||||
IO io;
|
||||
|
||||
ExecutionSupport() : memory(registers), flow_controller(memory, registers, status) {}
|
||||
|
||||
void clear() {
|
||||
memory.clear();
|
||||
}
|
||||
};
|
||||
|
||||
struct FailedExecution {
|
||||
std::string test_name;
|
||||
std::string reason;
|
||||
InstructionSet::x86::Instruction<false> instruction;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@interface i8088Tests : XCTestCase
|
||||
@end
|
||||
|
||||
@implementation i8088Tests
|
||||
@implementation i8088Tests {
|
||||
ExecutionSupport execution_support;
|
||||
std::vector<FailedExecution> execution_failures;
|
||||
}
|
||||
|
||||
- (NSArray<NSString *> *)testFiles {
|
||||
NSString *path = [NSString stringWithUTF8String:TestSuiteHome];
|
||||
NSSet *allowList = [NSSet setWithArray:@[
|
||||
// Current execution failures:
|
||||
// @"27.json.gz", // DAA
|
||||
// @"2F.json.gz", // DAS
|
||||
// @"D4.json.gz", // AAM
|
||||
// @"F6.7.json.gz", // IDIV
|
||||
// @"F7.7.json.gz", // IDIV
|
||||
]];
|
||||
|
||||
NSSet *ignoreList = nil;
|
||||
@ -58,22 +346,35 @@ constexpr char TestSuiteHome[] = "/Users/tharte/Projects/ProcessorTests/8088/v1"
|
||||
return [fullPaths sortedArrayUsingSelector:@selector(compare:)];
|
||||
}
|
||||
|
||||
- (NSString *)toString:(const InstructionSet::x86::Instruction<false> &)instruction offsetLength:(int)offsetLength immediateLength:(int)immediateLength {
|
||||
- (NSArray<NSDictionary *> *)testsInFile:(NSString *)file {
|
||||
NSData *data = [NSData dataWithContentsOfGZippedFile:file];
|
||||
return [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
|
||||
}
|
||||
|
||||
- (NSDictionary *)metadata {
|
||||
NSString *path = [[NSString stringWithUTF8String:TestSuiteHome] stringByAppendingPathComponent:@"8088.json"];
|
||||
return [NSJSONSerialization JSONObjectWithData:[NSData dataWithContentsOfGZippedFile:path] options:0 error:nil];
|
||||
}
|
||||
|
||||
- (NSString *)toString:(const std::pair<int, InstructionSet::x86::Instruction<false>> &)instruction offsetLength:(int)offsetLength immediateLength:(int)immediateLength {
|
||||
const auto operation = to_string(instruction, InstructionSet::x86::Model::i8086, offsetLength, immediateLength);
|
||||
return [[NSString stringWithUTF8String:operation.c_str()] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
|
||||
}
|
||||
|
||||
- (bool)applyDecodingTest:(NSDictionary *)test file:(NSString *)file assert:(BOOL)assert {
|
||||
using Decoder = InstructionSet::x86::Decoder<InstructionSet::x86::Model::i8086>;
|
||||
Decoder decoder;
|
||||
|
||||
// Build a vector of the instruction bytes; this makes manual step debugging easier.
|
||||
NSArray<NSNumber *> *encoding = test[@"bytes"];
|
||||
- (std::vector<uint8_t>)bytes:(NSArray<NSNumber *> *)encoding {
|
||||
std::vector<uint8_t> data;
|
||||
data.reserve(encoding.count);
|
||||
for(NSNumber *number in encoding) {
|
||||
data.push_back([number intValue]);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
- (bool)applyDecodingTest:(NSDictionary *)test file:(NSString *)file assert:(BOOL)assert {
|
||||
InstructionSet::x86::Decoder<InstructionSet::x86::Model::i8086> decoder;
|
||||
|
||||
// Build a vector of the instruction bytes; this makes manual step debugging easier.
|
||||
const auto data = [self bytes:test[@"bytes"]];
|
||||
auto hex_instruction = [&]() -> NSString * {
|
||||
NSMutableString *hexInstruction = [[NSMutableString alloc] init];
|
||||
for(uint8_t byte: data) {
|
||||
@ -83,30 +384,31 @@ constexpr char TestSuiteHome[] = "/Users/tharte/Projects/ProcessorTests/8088/v1"
|
||||
};
|
||||
|
||||
const auto decoded = decoder.decode(data.data(), data.size());
|
||||
const bool sizeMatched = decoded.first == data.size();
|
||||
if(assert) {
|
||||
XCTAssert(
|
||||
decoded.first == [encoding count],
|
||||
sizeMatched,
|
||||
"Wrong length of instruction decoded for %@ — decoded %d rather than %lu from %@; file %@",
|
||||
test[@"name"],
|
||||
decoded.first,
|
||||
(unsigned long)[encoding count],
|
||||
(unsigned long)data.size(),
|
||||
hex_instruction(),
|
||||
file
|
||||
);
|
||||
}
|
||||
if(decoded.first != [encoding count]) {
|
||||
if(!sizeMatched) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// The decoder doesn't preserve the original offset length, which makes no functional difference but
|
||||
// does affect the way that offsets are printed in the test set.
|
||||
NSSet<NSString *> *decodings = [NSSet setWithObjects:
|
||||
[self toString:decoded.second offsetLength:4 immediateLength:4],
|
||||
[self toString:decoded.second offsetLength:2 immediateLength:4],
|
||||
[self toString:decoded.second offsetLength:0 immediateLength:4],
|
||||
[self toString:decoded.second offsetLength:4 immediateLength:2],
|
||||
[self toString:decoded.second offsetLength:2 immediateLength:2],
|
||||
[self toString:decoded.second offsetLength:0 immediateLength:2],
|
||||
[self toString:decoded offsetLength:4 immediateLength:4],
|
||||
[self toString:decoded offsetLength:2 immediateLength:4],
|
||||
[self toString:decoded offsetLength:0 immediateLength:4],
|
||||
[self toString:decoded offsetLength:4 immediateLength:2],
|
||||
[self toString:decoded offsetLength:2 immediateLength:2],
|
||||
[self toString:decoded offsetLength:0 immediateLength:2],
|
||||
nil];
|
||||
|
||||
auto compare_decoding = [&](NSString *name) -> bool {
|
||||
@ -117,11 +419,7 @@ constexpr char TestSuiteHome[] = "/Users/tharte/Projects/ProcessorTests/8088/v1"
|
||||
|
||||
// Attempt clerical reconciliation:
|
||||
//
|
||||
// TEMPORARY HACK: the test set incorrectly states 'bp+si' whenever it means 'bp+di'.
|
||||
// Though it also uses 'bp+si' correctly when it means 'bp+si'. Until fixed, take
|
||||
// a pass on potential issues there.
|
||||
//
|
||||
// SEPARATELY: The test suite retains a distinction between SHL and SAL, which the decoder doesn't. So consider that
|
||||
// The test suite retains a distinction between SHL and SAL, which the decoder doesn't. So consider that
|
||||
// a potential point of difference.
|
||||
//
|
||||
// Also, the decoder treats INT3 and INT 3 as the same thing. So allow for a meshing of those.
|
||||
@ -129,14 +427,11 @@ constexpr char TestSuiteHome[] = "/Users/tharte/Projects/ProcessorTests/8088/v1"
|
||||
while(!isEqual && adjustment) {
|
||||
NSString *alteredName = [test[@"name"] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
|
||||
|
||||
if(adjustment & 4) {
|
||||
alteredName = [alteredName stringByReplacingOccurrencesOfString:@"bp+si" withString:@"bp+di"];
|
||||
}
|
||||
if(adjustment & 2) {
|
||||
alteredName = [alteredName stringByReplacingOccurrencesOfString:@"shl" withString:@"sal"];
|
||||
}
|
||||
if(adjustment & 1) {
|
||||
alteredName = [alteredName stringByReplacingOccurrencesOfString:@"int3" withString:@"int 03h"];
|
||||
alteredName = [alteredName stringByReplacingOccurrencesOfString:@"int3" withString:@"int 3h"];
|
||||
}
|
||||
|
||||
isEqual = compare_decoding(alteredName);
|
||||
@ -146,43 +441,232 @@ constexpr char TestSuiteHome[] = "/Users/tharte/Projects/ProcessorTests/8088/v1"
|
||||
if(assert) {
|
||||
XCTAssert(
|
||||
isEqual,
|
||||
"%@ doesn't match %@ or similar, was %@ within %@",
|
||||
"%@ doesn't match %@ or similar, was %@",
|
||||
test[@"name"],
|
||||
[decodings anyObject],
|
||||
hex_instruction(),
|
||||
file
|
||||
hex_instruction()
|
||||
);
|
||||
}
|
||||
|
||||
return isEqual;
|
||||
}
|
||||
|
||||
- (void)testDecoding {
|
||||
NSMutableSet<NSString *> *failures = [[NSMutableSet alloc] init];
|
||||
NSArray<NSString *> *testFiles = [self testFiles];
|
||||
- (void)populate:(Registers &)registers status:(InstructionSet::x86::Status &)status value:(NSDictionary *)value {
|
||||
registers.ax_.full = [value[@"ax"] intValue];
|
||||
registers.bx_.full = [value[@"bx"] intValue];
|
||||
registers.cx_.full = [value[@"cx"] intValue];
|
||||
registers.dx_.full = [value[@"dx"] intValue];
|
||||
|
||||
for(NSString *file in testFiles) {
|
||||
NSData *data = [NSData dataWithContentsOfGZippedFile:file];
|
||||
NSArray<NSDictionary *> *testsInFile = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
|
||||
NSUInteger successes = 0;
|
||||
for(NSDictionary *test in testsInFile) {
|
||||
registers.bp_ = [value[@"bp"] intValue];
|
||||
registers.cs_ = [value[@"cs"] intValue];
|
||||
registers.di_ = [value[@"di"] intValue];
|
||||
registers.ds_ = [value[@"ds"] intValue];
|
||||
registers.es_ = [value[@"es"] intValue];
|
||||
registers.si_ = [value[@"si"] intValue];
|
||||
registers.sp_ = [value[@"sp"] intValue];
|
||||
registers.ss_ = [value[@"ss"] intValue];
|
||||
registers.ip_ = [value[@"ip"] intValue];
|
||||
|
||||
const uint16_t flags = [value[@"flags"] intValue];
|
||||
status.set(flags);
|
||||
|
||||
// Apply a quick test of flag packing/unpacking.
|
||||
constexpr auto defined_flags = static_cast<uint16_t>(
|
||||
InstructionSet::x86::ConditionCode::Carry |
|
||||
InstructionSet::x86::ConditionCode::Parity |
|
||||
InstructionSet::x86::ConditionCode::AuxiliaryCarry |
|
||||
InstructionSet::x86::ConditionCode::Zero |
|
||||
InstructionSet::x86::ConditionCode::Sign |
|
||||
InstructionSet::x86::ConditionCode::Trap |
|
||||
InstructionSet::x86::ConditionCode::Interrupt |
|
||||
InstructionSet::x86::ConditionCode::Direction |
|
||||
InstructionSet::x86::ConditionCode::Overflow
|
||||
);
|
||||
XCTAssert((status.get() & defined_flags) == (flags & defined_flags),
|
||||
"Set status of %04x was returned as %04x",
|
||||
flags & defined_flags,
|
||||
(status.get() & defined_flags)
|
||||
);
|
||||
}
|
||||
|
||||
- (void)applyExecutionTest:(NSDictionary *)test metadata:(NSDictionary *)metadata {
|
||||
InstructionSet::x86::Decoder<InstructionSet::x86::Model::i8086> decoder;
|
||||
const auto data = [self bytes:test[@"bytes"]];
|
||||
const auto decoded = decoder.decode(data.data(), data.size());
|
||||
|
||||
execution_support.clear();
|
||||
|
||||
const uint16_t flags_mask = metadata[@"flags-mask"] ? [metadata[@"flags-mask"] intValue] : 0xffff;
|
||||
NSDictionary *const initial_state = test[@"initial"];
|
||||
NSDictionary *const final_state = test[@"final"];
|
||||
|
||||
// Apply initial state.
|
||||
InstructionSet::x86::Status initial_status;
|
||||
for(NSArray<NSNumber *> *ram in initial_state[@"ram"]) {
|
||||
execution_support.memory.seed([ram[0] intValue], [ram[1] intValue]);
|
||||
}
|
||||
for(NSArray<NSNumber *> *ram in final_state[@"ram"]) {
|
||||
execution_support.memory.touch([ram[0] intValue]);
|
||||
}
|
||||
Registers initial_registers;
|
||||
[self populate:initial_registers status:initial_status value:initial_state[@"regs"]];
|
||||
execution_support.status = initial_status;
|
||||
execution_support.registers = initial_registers;
|
||||
|
||||
// Execute instruction.
|
||||
//
|
||||
// TODO: enquire of the actual mechanism of repetition; if it were stateful as below then
|
||||
// would it survive interrupts? So is it just IP adjustment?
|
||||
execution_support.registers.ip_ += decoded.first;
|
||||
do {
|
||||
execution_support.flow_controller.begin_instruction();
|
||||
InstructionSet::x86::perform<InstructionSet::x86::Model::i8086>(
|
||||
decoded.second,
|
||||
execution_support.status,
|
||||
execution_support.flow_controller,
|
||||
execution_support.registers,
|
||||
execution_support.memory,
|
||||
execution_support.io
|
||||
);
|
||||
} while (execution_support.flow_controller.should_repeat());
|
||||
|
||||
// Compare final state.
|
||||
Registers intended_registers;
|
||||
InstructionSet::x86::Status intended_status;
|
||||
|
||||
bool ramEqual = true;
|
||||
for(NSArray<NSNumber *> *ram in final_state[@"ram"]) {
|
||||
const uint32_t address = [ram[0] intValue];
|
||||
|
||||
uint8_t mask = 0xff;
|
||||
if(const auto tag = execution_support.memory.tags.find(address); tag != execution_support.memory.tags.end()) {
|
||||
switch(tag->second) {
|
||||
default: break;
|
||||
case Memory::Tag::FlagsH: mask = flags_mask >> 8; break;
|
||||
case Memory::Tag::FlagsL: mask = flags_mask & 0xff; break;
|
||||
}
|
||||
}
|
||||
|
||||
if((execution_support.memory.memory[address] & mask) != ([ram[1] intValue] & mask)) {
|
||||
ramEqual = false;
|
||||
}
|
||||
}
|
||||
|
||||
[self populate:intended_registers status:intended_status value:final_state[@"regs"]];
|
||||
const bool registersEqual = intended_registers == execution_support.registers;
|
||||
const bool statusEqual = (intended_status.get() & flags_mask) == (execution_support.status.get() & flags_mask);
|
||||
|
||||
if(!statusEqual || !registersEqual || !ramEqual) {
|
||||
FailedExecution failure;
|
||||
failure.instruction = decoded.second;
|
||||
failure.test_name = std::string([test[@"name"] UTF8String]);
|
||||
|
||||
NSMutableArray<NSString *> *reasons = [[NSMutableArray alloc] init];
|
||||
if(!statusEqual) {
|
||||
Status difference;
|
||||
difference.set((intended_status.get() ^ execution_support.status.get()) & flags_mask);
|
||||
[reasons addObject:
|
||||
[NSString stringWithFormat:@"status differs; errors in %s",
|
||||
difference.to_string().c_str()]];
|
||||
}
|
||||
if(!registersEqual) {
|
||||
NSMutableArray<NSString *> *registers = [[NSMutableArray alloc] init];
|
||||
#define Reg(x) \
|
||||
if(intended_registers.x() != execution_support.registers.x()) \
|
||||
[registers addObject: \
|
||||
[NSString stringWithFormat: \
|
||||
@#x" is %04x rather than %04x", execution_support.registers.x(), intended_registers.x()]];
|
||||
|
||||
Reg(ax);
|
||||
Reg(cx);
|
||||
Reg(dx);
|
||||
Reg(bx);
|
||||
Reg(sp);
|
||||
Reg(bp);
|
||||
Reg(si);
|
||||
Reg(di);
|
||||
Reg(ip);
|
||||
Reg(es);
|
||||
Reg(cs);
|
||||
Reg(ds);
|
||||
Reg(ss);
|
||||
|
||||
#undef Reg
|
||||
[reasons addObject:[NSString stringWithFormat:
|
||||
@"registers don't match: %@", [registers componentsJoinedByString:@", "]
|
||||
]];
|
||||
}
|
||||
if(!ramEqual) {
|
||||
[reasons addObject:@"RAM contents don't match"];
|
||||
}
|
||||
|
||||
failure.reason = std::string([reasons componentsJoinedByString:@"; "].UTF8String);
|
||||
execution_failures.push_back(std::move(failure));
|
||||
}
|
||||
}
|
||||
|
||||
- (void)printFailures:(NSArray<NSString *> *)failures {
|
||||
NSLog(
|
||||
@"%ld failures out of %ld tests: %@",
|
||||
failures.count,
|
||||
[self testFiles].count,
|
||||
[failures sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)]);
|
||||
}
|
||||
|
||||
- (void)testDecoding {
|
||||
NSMutableArray<NSString *> *failures = [[NSMutableArray alloc] init];
|
||||
for(NSString *file in [self testFiles]) @autoreleasepool {
|
||||
for(NSDictionary *test in [self testsInFile:file]) {
|
||||
// A single failure per instruction is fine.
|
||||
if(![self applyDecodingTest:test file:file assert:YES]) {
|
||||
[failures addObject:file];
|
||||
|
||||
// Attempt a second decoding, to provide a debugger hook.
|
||||
[self applyDecodingTest:test file:file assert:NO];
|
||||
|
||||
break;
|
||||
}
|
||||
++successes;
|
||||
}
|
||||
if(successes != [testsInFile count]) {
|
||||
NSLog(@"Failed after %ld successes", successes);
|
||||
}
|
||||
}
|
||||
|
||||
NSLog(@"%ld failures out of %ld tests: %@", failures.count, testFiles.count, [[failures allObjects] sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)]);
|
||||
[self printFailures:failures];
|
||||
}
|
||||
|
||||
- (void)testExecution {
|
||||
NSDictionary *metadata = [self metadata];
|
||||
NSMutableArray<NSString *> *failures = [[NSMutableArray alloc] init];
|
||||
|
||||
for(NSString *file in [self testFiles]) @autoreleasepool {
|
||||
const auto failures_before = execution_failures.size();
|
||||
|
||||
// Determine the metadata key.
|
||||
NSString *const name = [file lastPathComponent];
|
||||
NSRange first_dot = [name rangeOfString:@"."];
|
||||
NSString *metadata_key = [name substringToIndex:first_dot.location];
|
||||
|
||||
// Grab the metadata. If it wants a reg field, inspect a little further.
|
||||
NSDictionary *test_metadata = metadata[metadata_key];
|
||||
if(test_metadata[@"reg"]) {
|
||||
test_metadata = test_metadata[@"reg"][[NSString stringWithFormat:@"%c", [name characterAtIndex:first_dot.location+1]]];
|
||||
}
|
||||
|
||||
int index = 0;
|
||||
for(NSDictionary *test in [self testsInFile:file]) {
|
||||
[self applyExecutionTest:test metadata:test_metadata];
|
||||
++index;
|
||||
}
|
||||
|
||||
if (execution_failures.size() != failures_before) {
|
||||
[failures addObject:file];
|
||||
}
|
||||
}
|
||||
|
||||
XCTAssertEqual(execution_failures.size(), 0);
|
||||
|
||||
for(const auto &failure: execution_failures) {
|
||||
NSLog(@"Failed %s — %s", failure.test_name.c_str(), failure.reason.c_str());
|
||||
}
|
||||
|
||||
NSLog(@"Files with failures were: %@", failures);
|
||||
}
|
||||
|
||||
@end
|
||||
|
@ -1,103 +0,0 @@
|
||||
//
|
||||
// x86DataPointerTests.m
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 27/02/2022.
|
||||
// Copyright 2022 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
#include "../../../InstructionSets/x86/DataPointerResolver.hpp"
|
||||
#include <map>
|
||||
|
||||
using namespace InstructionSet::x86;
|
||||
|
||||
@interface x86DataPointerTests : XCTestCase
|
||||
@end
|
||||
|
||||
@implementation x86DataPointerTests
|
||||
|
||||
- (void)test16bitSize1 {
|
||||
const DataPointer indirectPointer(
|
||||
Source::eAX, Source::eDI, 0
|
||||
);
|
||||
const DataPointer registerPointer(
|
||||
Source::eBX
|
||||
);
|
||||
|
||||
struct Registers {
|
||||
uint16_t ax = 0x1234, di = 0x00ee;
|
||||
uint8_t bl = 0xaa;
|
||||
|
||||
template <typename DataT, Register r> DataT read() {
|
||||
assert(is_sized<DataT>(r));
|
||||
switch(r) {
|
||||
case Register::AX: return ax;
|
||||
case Register::BL: return bl;
|
||||
case Register::DI: return di;
|
||||
default: return 0;
|
||||
}
|
||||
}
|
||||
template <typename DataT, Register r> void write(DataT value) {
|
||||
assert(is_sized<DataT>(r));
|
||||
switch(r) {
|
||||
case Register::BL: bl = value; break;
|
||||
default: assert(false);
|
||||
}
|
||||
}
|
||||
} registers;
|
||||
|
||||
struct Memory {
|
||||
std::map<uint32_t, uint8_t> data;
|
||||
|
||||
template<typename DataT> DataT read(Source, uint32_t address) {
|
||||
if(address == 0x1234 + 0x00ee) return 0xff;
|
||||
return 0;
|
||||
}
|
||||
template<typename DataT> void write(Source, uint32_t address, DataT value) {
|
||||
data[address] = value;
|
||||
}
|
||||
} memory;
|
||||
|
||||
// TODO: construct this more formally; the code below just assumes size = 1, which is not a contractual guarantee.
|
||||
const auto instruction = Instruction<false>();
|
||||
|
||||
using Resolver = DataPointerResolver<Model::i8086, Registers, Memory>;
|
||||
const uint8_t memoryValue = Resolver::read<uint8_t>(
|
||||
registers,
|
||||
memory,
|
||||
instruction,
|
||||
indirectPointer
|
||||
);
|
||||
registers.ax = 0x0100;
|
||||
Resolver::write<uint8_t>(
|
||||
registers,
|
||||
memory,
|
||||
instruction,
|
||||
indirectPointer,
|
||||
0xef
|
||||
);
|
||||
|
||||
XCTAssertEqual(memoryValue, 0xff);
|
||||
XCTAssertEqual(memory.data[0x01ee], 0xef);
|
||||
|
||||
const uint8_t registerValue = Resolver::read<uint8_t>(
|
||||
registers,
|
||||
memory,
|
||||
instruction,
|
||||
registerPointer
|
||||
);
|
||||
Resolver::write<uint8_t>(
|
||||
registers,
|
||||
memory,
|
||||
instruction,
|
||||
registerPointer,
|
||||
0x93
|
||||
);
|
||||
|
||||
XCTAssertEqual(registerValue, 0xaa);
|
||||
XCTAssertEqual(registers.bl, 0x93);
|
||||
}
|
||||
|
||||
@end
|
@ -12,7 +12,6 @@
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
#include "../../../InstructionSets/x86/Decoder.hpp"
|
||||
#include "../../../InstructionSets/x86/DataPointerResolver.hpp"
|
||||
|
||||
using namespace InstructionSet::x86;
|
||||
|
||||
@ -411,7 +410,7 @@ decode(const std::initializer_list<uint8_t> &stream, bool set_32_bit = false) {
|
||||
// add DWORD PTR [edi-0x42],0x9f683aa9
|
||||
// lock jp 0xfffffff0 (from 0000000e)
|
||||
test(instructions[0], DataSize::DWord, Operation::INC, Source::eDX);
|
||||
XCTAssertEqual(instructions[0].data_segment(), Source::CS);
|
||||
XCTAssertEqual(instructions[0].segment_override(), Source::CS);
|
||||
test(instructions[1], DataSize::Byte, Operation::OR, Source::Immediate, Source::eAX, 0x9);
|
||||
test(instructions[2], DataSize::DWord, Operation::ADD, Source::Immediate, ScaleIndexBase(Source::eDI), 0x9f683aa9, -0x42);
|
||||
test(instructions[3], Operation::JP, 0, -30);
|
||||
@ -422,7 +421,7 @@ decode(const std::initializer_list<uint8_t> &stream, bool set_32_bit = false) {
|
||||
// stos BYTE PTR es:[edi],al
|
||||
// pusha
|
||||
test(instructions[4], DataSize::Byte, Operation::MOV, Source::Immediate, Source::AH, 0xc1);
|
||||
XCTAssertEqual(instructions[4].data_segment(), Source::DS);
|
||||
XCTAssertEqual(instructions[4].segment_override(), Source::DS);
|
||||
test(instructions[5], DataSize::Word, Operation::POP, Source::None, Source::DS);
|
||||
test(instructions[6], DataSize::Byte, Operation::STOS);
|
||||
test(instructions[7], Operation::PUSHA);
|
||||
@ -465,7 +464,7 @@ decode(const std::initializer_list<uint8_t> &stream, bool set_32_bit = false) {
|
||||
test(instructions[21], DataSize::Byte, Operation::XOR, Source::Immediate, Source::eAX, 0x45);
|
||||
test(instructions[22], DataSize::DWord, Operation::LDS, ScaleIndexBase(Source::eCX), Source::eDX);
|
||||
test(instructions[23], DataSize::Byte, Operation::MOV, Source::eAX, Source::DirectAddress, 0xe4dba6d3);
|
||||
XCTAssertEqual(instructions[23].data_segment(), Source::DS);
|
||||
XCTAssertEqual(instructions[23].segment_override(), Source::DS);
|
||||
|
||||
// pop ds
|
||||
// movs DWORD PTR es:[edi],DWORD PTR ds:[esi]
|
||||
|
@ -327,7 +327,7 @@ template <Personality personality, typename T, bool uses_ready_line> void Proces
|
||||
|
||||
// All flags are set based only on the decimal result.
|
||||
flags_.zero_result = result;
|
||||
flags_.carry = Numeric::carried_out<7>(a_, operand_, result);
|
||||
flags_.carry = Numeric::carried_out<true, 7>(a_, operand_, result);
|
||||
flags_.negative_result = result;
|
||||
flags_.overflow = (( (result ^ a_) & (result ^ operand_) ) & 0x80) >> 1;
|
||||
|
||||
@ -377,7 +377,7 @@ template <Personality personality, typename T, bool uses_ready_line> void Proces
|
||||
if(flags_.decimal && has_decimal_mode(personality)) {
|
||||
uint8_t result = a_ + operand_ + flags_.carry;
|
||||
flags_.zero_result = result;
|
||||
flags_.carry = Numeric::carried_out<7>(a_, operand_, result);
|
||||
flags_.carry = Numeric::carried_out<true, 7>(a_, operand_, result);
|
||||
|
||||
// General ADC logic:
|
||||
//
|
||||
|
Loading…
x
Reference in New Issue
Block a user